Javascript – Node.JS Code Improvement (protobuf, tls)

I'm working with an environment that allows me to execute node.js code from a single file without installing additional modules, My job is to write a simple SSL socket client that sends protobuf messages, receives and decodes responses in the same format.

What I've developed in this code sample:

  1. Protobuf decoder / encoder (I have implemented only types that I will use);
  2. SSL Socket class;

I'm a newbie in node.js (6 hours experience: D) so you need to show me weak parts of this code and share best practices with me:

const tls = require("tls");

const HOST = "example.com";
const PORT = 12345;

const ProtoTypes = Object.freeze({"Varint": 0, "_64bit": 1, "LengthDelimited": 2, "_32bit": 5});

class ProtoField {
    constructor (type, tag, value=null) {
        if (isNaN(tag) || tag < 0)
            throw new Error("Invalid tag");

        this.type = type;
        this.tag = tag;
        this.value = value;
    }

    encodePrefix() {
        return VarintField.encode((this.tag << 3) | this.type);
    }

    static decodePrefix(stream, position=0) {
        var decoded_prefix = VarintField.decode(stream, position);
        return {
            type: decoded_prefix.value & 7,
            tag: decoded_prefix.value >> 3,
            length: decoded_prefix.length
        }
    }

    encode() {
        var prefix = this.encodePrefix();

        return Buffer.concat((prefix, this.constructor.encode(this.value)));
    }

    decode(stream, position=0) {
        var decoded = this.constructor.decode(stream, position);
        this.value = decoded.value;

        return decoded.length;
    }
}

class VarintField extends ProtoField {
    constructor(tag, value=null) {
        if (value != null && isNaN(value))
            throw new Error("Invalid value");

        super(ProtoTypes.Varint, tag, value);
    }

    static encode(value) {
        var number = value, buf = ();

        for (;;) {
            var toWrite = number & 0x7F;
            number >>= 7;
            if (number != 0)
                buf.push(toWrite | 0x80);
            else {
                buf.push(toWrite);
                break;
            }
        }

        return Buffer.from(buf);
    }

    static decode(stream, position=0) {
        var number =  0, bytes_read = 0;

        for (var shift = 0; position + bytes_read < stream.length; bytes_read++, shift += 7) {
            var b = stream(position + bytes_read);
            number |= (b & 0x7f) << shift;
            if ((b & 0x80) == 0) {
                bytes_read += 1;
                break;
            }
        }

        return {
            value: number,
            length: bytes_read
        }
    }
}

class BytesField extends ProtoField {
    constructor(tag, value=null) {
        if (value != null && !Buffer.isBuffer(value))
            throw new Error("Invalid value");

        super(ProtoTypes.LengthDelimited, tag, value);
    }

    static encode(value) {
        return Buffer.concat((VarintField.encode(value.length), Buffer.from(value)));
    }

    static decode(stream, position=0) {
        var length = VarintField.decode(stream, position);
        return {
            value: stream.slice(position + length.length, position + length.length + length.value),
            length: length.length + length.value
        }
    }
}

class StringField extends ProtoField {
    constructor(tag, value=null) {
        if (value != null && String(value) !== value)
            throw new Error("Invalid value");

        super(ProtoTypes.LengthDelimited, tag, value);
    }

    static encode(value) {
        return Buffer.concat((VarintField.encode(value.length), Buffer.from(value, "utf-8")));
    }

    static decode(stream, position=0) {
        var length = VarintField.decode(stream, position);
        return {
            value: stream.slice(position + length.length, position + length.length + length.value).toString("utf-8"),
            length: length.length + length.value
        }
    }
}

class _32bitField {
    static decode(stream, position=0) {
        return {value: null, length: 4};
    }
}

class _64bitField {
    static decode(stream, position=0) {
        return {value: null, length: 8};
    }
}

const ProtoTypesDefaultClasses = Object.freeze({
    (ProtoTypes.Varint): VarintField,
    (ProtoTypes.LengthDelimited): BytesField,
    (ProtoTypes._32bit): _32bitField,
    (ProtoTypes._64bit): _64bitField
});

class Message {
    constructor() {
        this.fields = ();
    }

    addField(field) {
        this.fields(field.tag) = field;
        return field;
    }

    encode() {
        var buffers = ();
        this.fields.forEach(function(field) {
            if (field.value != null)
                buffers.push(field.encode());
        });
        return Buffer.concat(buffers);
    }

    decode(stream, position=0, length=0) {
        var bytes_read = 0, length = length > 0 ? length : stream.length - position;

        for (; bytes_read < length;) {
            var prefix = ProtoField.decodePrefix(stream, position + bytes_read);
            if (this.fields(prefix.tag) != undefined)
                bytes_read += this.fields(prefix.tag).decode(stream, position + bytes_read + prefix.length) + prefix.length;
            else
                bytes_read += ProtoTypesDefaultClasses(prefix.type).decode(stream, position + bytes_read + prefix.length).length + prefix.length;
        }

        return bytes_read;
    } 
}

class EmbeddedMessageField extends ProtoField {
    constructor(tag, value) {
        if (!(value instanceof Message))
            throw new Error("Invalid value");

        super(ProtoTypes.LengthDelimited, tag, value);
    }

    static encode(value) {
        return BytesField.encode(value.encode());
    }

    decode(stream, position=0) {
        var length = VarintField.decode(stream, position);

        return length.length + this.value.decode(stream, position + 1, length.value)
    }
}

class ExampleEmbededMessage extends Message {
    constructor(f1, f2, f3) {
        super();

        this.e_field1 = this.addField(new BytesField(1, f1));
        this.e_field2 = this.addField(new VarintField(2, f2));
        this.e_field3 = this.addField(new StringField(4, f3));
    }
}

class ExampleRequestMessage extends Message {
    constructor(f0, f1, f2, f3) {
        super();

        this.field1 = this.addField(new StringField(1, f0));
        this.embedded = this.addField(new EmbeddedMessageField(2, new ExampleEmbededMessage(f1, f2, f3)));
        this.constant_field = this.addField(new StringField(3, "constant"));
    }
}

class ExampleResponseMessage extends Message {
    constructor() {
        super();

        this.resp_field1 = this.addField(new StringField(1));
        this.resp_field2 = this.addField(new VarintField(2));
        this.resp_field15 = this.addField(new StringField(15));
    }
}

class Packet {
    constructor(id, message) {
        this.id = id;
        this.message = message;
    }

    encode() {
        return Buffer.concat((Buffer.from((this.id)), this.message.encode()));
    }

    decode(stream) {
        if (stream.length == 0)
            return 0;
        this.id = stream(0);
        return this.message.decode(stream, 1) + 1;
    }
}

class TLSClient {
    constructor(host, port) {
        this.host = host;
        this.port = port;
        this.options = {rejectUnauthorized: false};
        this.init()
    }

    init() {
        var client = this;
        this.socket = tls.connect(client.port, client.host, client.options);
    }

    send(packet) {
        var client = this;
        return new Promise((resolve, reject) => {
            var encoded_packet = packet.encode();
            var length = Buffer.alloc(4);
            length.writeUInt32LE(encoded_packet.length);

            client.socket.write(Buffer.concat((length, encoded_packet)));

            client.socket.on("data", (data) => {
                resolve(data);
                client.socket.destroy();
            });
            client.socket.on("error", (err) => {
                reject(err);
            });
        });
    }
}

var f0 = "string1", f1 = Buffer.from((0, 1, 2, 3, 4, 5)), f2 = 54321, f3 = "string2";
var request_packet = new Packet(13, new ExampleRequestMessage(f0, f1, f2, f3));
var response_packet = new Packet(13, new ExampleResponseMessage());

var sock = new TLSClient(HOST, PORT);

sock.send(request_packet)
.then((data) => {
    response_packet.decode(data, 4);
    if (response_packet.resp_field2.value > 0)
        console.log("Great!");
    else
        console.log("Zero.");
})
.catch((err) => {
    console.log(err);
});

P.S. I know it's 300 lines of code, I've tried to cut as much as possible ..

How to convert protobuf wallet format to private key

first,
I did this step "enc -d -aes-256-cbc -a -in-bitcoin-wallet-backup> bitcoin_decrypted" in openssl and typed in the password that org.bitcoin.production gave me, so I think that my password works I can see 2 lines of foreign language with a few letters and numbers in between, so I assume it's the protobuf format.
Now as listed is the second step
Install bitcoinj:
Git clone https://github.com/bitcoinj/bitcoinj.git
cd bitcoinj
Git Git –all
Git Checkout v0.14.4
MVN installation (fails on GroupTest in Windows Bash, but works anyway)
CD Tools
./wallet-tool

But my question is, where should I enter all these commands? in the same opensll page where I see the foreign language or elsewhere?

Why Protobuf is called a binary format, although we write it in text format similar to json

This is clearer when you compare two similar data items to non-text data components.

For example, the following JSON is ALL text:

{
"NumberOfClients": 20
}

The 20 consists of two separate characters, but would be represented in Protobuf as an actual binary integer, which in this case would be a single byte: 00010100

Also, I'm not sure if you understand Protobuf correctly when you compare apples and oranges in your question. They compare JSON dates to a protéguf scheme

Networking – Protobuf Vs. Flat buffer for games?

I've done some research on protopuffers and flat buffers, but I've seen conflicting and potentially outdated information.

Is it correct to say that the main tradeoff is that protobuffers have a smaller wire size, while flat buffers are more accessible?

So, if a project depends more on network performance than on local data read times, would protobuffers be the obvious choice? Or do I still miss other factors?

Protobuf: compile spec once, use generated code anywhere?

Once written, a protobuf specification can be created using Protoc to a variety of implementations in different languages ​​(eg Python, C ++, go). That's great. To compile the generated code, you need a protobuf development library, and if I am not mistaken, it will take some running time.

Is there a way to generate the implementation code in a pure linguistic, self-contained way? For example, I would compile and distribute my implementation in C ++ and Python, and users could use it simply by using the standard library.

If not, are there frameworks (similar in principle to Protobuf) that produce a serialization code in a specification that is self-contained and does not require other non-standard libraries for a particular language?

This question arises from the fact that lately I had to use protobuf on different systems and that there was a different development library available for each one. Should the specification be created separately on each system? What if the compilers have different versions?

Serialization – Can I add a new function for the struct generated by protobuf?

I use protobuf definitions to define message types.
Structure looks like

message1 {
}
Message2 {
Bytes msg1 = 1
}

So Message2 has a field that is a byte array representation of message1. I am:

  1. Create a msg1 structure instance
  2. serialization
  3. Encrypt byte array obtained in step 2
  4. Calculate the digital signature of the cipher generated in step 3

I want to see if it's possible to add a function for protobuf message1 that returns a serialized message1.