Setting and getting data from a string or stringstream

-1

I'm trying to write a reusable message object that would take its properties, convert them into a delimited string (using 0x1d group seperator), put that in a char buffer, and also be able to do the reverse (from char back to object).

This reason why I must do this is that I am completely limited by the OS capabilities to only send messages that are of type char and a fixed size, so this is my wrapper for that.

Here is what I have so far. Easy to pack.. now, how can I write a sensible way to unpack it. I am OK if every child of this class must manually unpack this data, but I just don't see how. I tried getline but then I end up with a string and will have to write many conversion functions.. There must be a simpler way.

Note I am in C++98.

#include <iostream>
#include <sstream>
#include <string.h>

class Msg {
    public:
        Msg(){
            delim = 0x1d;
        }
        int8_t ia;
        int16_t ib;
        int32_t ic;
        int64_t id;
        uint8_t ua;
        uint16_t ub;
        uint32_t uc;
        uint64_t ud;
        std::string str = "aaa bbb ccc dddd";
        char sz[64];
        char delim;

        // convert to a char buffer
        void ToBuffer(unsigned char* b, int s){
            std::stringstream ss;
            ss << ia << delim
               << ib << delim
               << ic << delim
               << id << delim
               << ua << delim
               << ub << delim
               << uc << delim
               << ud << delim
               << str << delim 
               << sz << delim;

            strncpy((char*)b, ss.str().c_str(), s);
            b[s-1] = '\0';
        }

        // convert from a char buffer
        void FromBuffer(unsigned char* b, int s){
            // what on earth to do here..
            // could use getline which returns a string after
            // each delimiter, then convert each string to the
            // value in a known order.. but at that point I may
            // as well have written this all in C... !
        }

        void Print(){
            std::cout 
                << " ia " << ia
                << " ib " << ib
                << " ic " << ic
                << " id " << id
                << " ua " << ua
                << " ub " << ub
                << " uc " << uc
                << " ud " << ud
                << " str " << str 
                << " sz "  << sz;
        }
};

int main()
{
    Msg msg;
    msg.ia = 0xFE;
    msg.ib = 0xFEFE;
    msg.ic = 0xFEFEFEFE;
    msg.id = 0xFEFEFEFEFEFEFEFE;
    msg.ua = 0xEE;
    msg.ub = 0xDEAD;
    msg.uc = 0xDEADBEEF;
    msg.ud = 0xDEADBEEFDEADBEEF;
    snprintf(msg.sz, 64, "this is a test");
    msg.Print();

    int s = 128;
    unsigned char b[s];
    msg.ToBuffer(b, s);

    Msg msg2;
    msg2.FromBuffer(b, s);
    //msg2.Print();


    return 0;
}
c++
c++98
asked on Stack Overflow Apr 19, 2020 by terratermatwoa • edited Apr 19, 2020 by cigien

1 Answer

1

Ok, so it works but it is quite ugly to put the buffer into a string stream just so you can use std::getline with a delimiter to extract bits and then use another string stream or std::stoi and friends to convert the items to the right types:

https://repl.it/repls/GainsboroInsecureEvents

    void FromBuffer(unsigned char* b, int s){
        std::string item;
        std::stringstream ss((char *)b);
        // You don't NEED to use std::stringstream to convert
        // the item to the primitive types - you could use
        // std::stoi, std::stol, std::stoll, etc but using a
        // std::stringstream makes it so you don't need to
        // know which primitive type the variable is
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> ia;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> ib;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> ic;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> id;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> ua;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> ub;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> uc;
        std::getline(ss,item,'\x1d'); std::stringstream(item) >> ud;
        // Until you get to here.  Then >> stops on a space
        // and all the sudden you can't use >> to get the data
        std::getline(ss,str,'\x1d');
        // And a C string is even worse because you need to
        // respect the length of the buffer by using strncpy
        std::getline(ss,item,'\x1d'); strncpy(sz,item.c_str(),64); sz[63] = '\0';
    }

So I think a much better way is to create a new ctype facet that uses a new delimiter and imbue the string stream with the new facet like was done here changing the delimiter for cin (c++)

That way we can just extract directly which is MUCH better:

https://repl.it/repls/GraveDraftyAdministrators

    void FromBuffer(unsigned char* b, int s){
        struct delimiter : std::ctype<char> {
          delimiter() : std::ctype<char>(get_table()) {}
          static mask const* get_table()
          {
            static mask rc[table_size];
            rc[0x1d] = std::ctype_base::space;
            return &rc[0];
          }
        };
        std::stringstream ss((char *)b);
        ss.imbue(std::locale(ss.getloc(), new delimiter));
        ss >> ia
           >> ib
           >> ic
           >> id
           >> ua
           >> ub
           >> uc
           >> ud
           >> str
           >> sz;
    }
answered on Stack Overflow Apr 20, 2020 by Jerry Jeremiah

User contributions licensed under CC BY-SA 3.0