Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Read "varint" from linux sockets

Tags:

c++

c

linux

sockets

I need to read a VarInts from linux sockets in C/C++. Any library, idea or something?

I tried reading and casting char to bool[8] to try without success to read a VarInt...

Also, this is for compatibility with new Minecraft 1.7.2 communication protocol, so, the documentation of the protocol may also help.

Let me explain my project: I'm making a Minecraft server software to run in my VPS (because java is too slow...) and I got stuck with the protocol. One thread waits for the connections and when it has a new connection, it creates a new Client object and starts the Client thread that starts communicating with the client.

I think that there is no need to show code. In case I'm wrong, tell me and I'll edit with some code.

like image 730
azteca1998 Avatar asked Nov 03 '13 21:11

azteca1998


1 Answers

First off, note that varints are sent as actual bytes, not strings of the characters 1 and 0.

For an unsigned varint, I believe the following will decode it for you, assuming you've got the varint data in a buffer pointed to by data. This example function returns the number of bytes decoded in the reference argument int decoded_bytes.

uint64_t decode_unsigned_varint( const uint8_t *const data, int &decoded_bytes )
{
    int i = 0;
    uint64_t decoded_value = 0;
    int shift_amount = 0;

    do 
    {
        decoded_value |= (uint64_t)(data[i] & 0x7F) << shift_amount;     
        shift_amount += 7;
    } while ( (data[i++] & 0x80) != 0 );

    decoded_bytes = i;
    return decoded_value;
}

To decode a signed varint, you can use this second function that calls the first:

int64_t decode_signed_varint( const uint8_t *const data, int &decoded_bytes )
{
    uint64_t unsigned_value = decode_unsigned_varint(data, decoded_bytes);
    return (int64_t)( unsigned_value & 1 ? ~(unsigned_value >> 1) 
                                         :  (unsigned_value >> 1) );
}

I believe both of these functions are correct. I did some basic testing with the code below to verify a couple datapoints from the Google page. The output is correct.

#include <stdint.h>
#include <iostream>


uint64_t decode_unsigned_varint( const uint8_t *const data, int &decoded_bytes )
{
    int i = 0;
    uint64_t decoded_value = 0;
    int shift_amount = 0;

    do 
    {
        decoded_value |= (uint64_t)(data[i] & 0x7F) << shift_amount;     
        shift_amount += 7;
    } while ( (data[i++] & 0x80) != 0 );

    decoded_bytes = i;
    return decoded_value;
}

int64_t decode_signed_varint( const uint8_t *const data, int &decoded_bytes )
{
    uint64_t unsigned_value = decode_unsigned_varint(data, decoded_bytes);
    return (int64_t)( unsigned_value & 1 ? ~(unsigned_value >> 1) 
                                         :  (unsigned_value >> 1) );
}



uint8_t ex_p300[] = { 0xAC, 0x02 };
uint8_t ex_n1  [] = { 0x01 };

using namespace std;

int main()
{
    int decoded_bytes_p300;
    uint64_t p300;

    p300 = decode_unsigned_varint( ex_p300, decoded_bytes_p300 );

    int decoded_bytes_n1;
    int64_t  n1;

    n1 = decode_signed_varint( ex_n1, decoded_bytes_n1 );

    cout << "p300 = " << p300 
         << "   decoded_bytes_p300 = " << decoded_bytes_p300 << endl;

    cout << "n1 = " << n1 
         << "   decoded_bytes_n1 = " << decoded_bytes_n1 << endl;

    return 0;
}

To encode varints, you could use the following functions. Note that the buffer uint8_t *const data should have room for at least 10 bytes, as the largest varint is 10 bytes long.
#include

// Encode an unsigned 64-bit varint.  Returns number of encoded bytes.
// 'buffer' must have room for up to 10 bytes.
int encode_unsigned_varint(uint8_t *const buffer, uint64_t value)
{
    int encoded = 0;

    do
    {
        uint8_t next_byte = value & 0x7F;
        value >>= 7;

        if (value)
            next_byte |= 0x80;

        buffer[encoded++] = next_byte;

    } while (value);


    return encoded;
}

// Encode a signed 64-bit varint.  Works by first zig-zag transforming
// signed value into an unsigned value, and then reusing the unsigned
// encoder.  'buffer' must have room for up to 10 bytes.
int encode_signed_varint(uint8_t *const buffer, int64_t value)
{
    uint64_t uvalue;

    uvalue = uint64_t( value < 0 ? ~(value << 1) : (value << 1) );

    return encode_unsigned_varint( buffer, uvalue );
}
like image 199
Joe Z Avatar answered Oct 21 '22 12:10

Joe Z