3

What is the Node.js equivalent of the fgetc() function in php? And how would I apply it to a socket?

I'm working on a node.js port of this php script: http://code.google.com/p/bf2php/source/browse/trunk/rcon/BF2RConBase.class.php

Basically it uses sockets to connect to Battlefield 2 based game servers. The function I'm looking at is:

protected function read($bare = false) {
    $delim = $bare ? "\n" : "\x04";
    for($buffer = ''; ($char = fgetc($this->socket)) != $delim; $buffer .= $char);
    return trim($buffer);
}

Its supposed to grab the first line directly from the socket (from what I gather) one character at a time up til the '\n'. I'm assuming the output is used for grabbing an encryption salt. The function is called in the socket connect event as part of the code that generates the encrypted password needed to login. Can anyone show me what a Node.js equivalent of this function might look like?

lee8oi
  • 1,169
  • 8
  • 12
  • While you port, you might find this helpful: https://github.com/cranic/node-phpjs – hakre Oct 08 '12 at 20:46
  • Thanks for your reply. I'm not sure I understand what that even does. Not much documentation there. But thanks again! – lee8oi Oct 08 '12 at 20:55
  • phpjs is all php function in javascript. that repro ports that library to nodejs. probably the fgetc port has code you might be looking for. its a bit over three corners, but probably helpful. you could also directly port PHP code to js because you have all the php functions in js. – hakre Oct 08 '12 at 20:56

1 Answers1

1

The docs have an excellent example of how to connect to a server over the network.

var net = require('net');
var client = net.connect({port: 8124},
    function() { //'connect' listener
  console.log('client connected');
  client.write('world!\r\n');
});
client.on('data', function(data) {
  console.log(data.toString());
  client.end();
});
client.on('end', function() {
  console.log('client disconnected');
});

Simply change the data event handler to buffer incoming data until you've recieved the information you want.

To do that, you'll want to know how to use a Buffer.


Here's a concrete example of how to buffer data from a stream and parse out messages delimited by a specific character. I notice in the linked PHP that the protocol you're trying to implement delimts messages with a EOT (0x04) character.

var net = require('net');


var max = 1024 * 1024 // 1 MB, the maximum amount of data that we will buffer (prevent a bad server from crashing us by filling up RAM)
    , allocate = 4096; // how much memory to allocate at once, 4 kB (there's no point in wasting 1 MB of RAM to buffer a few bytes)
    , buffer=new Buffer(allocate) // create a new buffer that allocates 4 kB to start
    , nread=0 // how many bytes we've buffered so far
    , nproc=0 // how many bytes in the buffer we've processed (to avoid looping over the entire buffer every time data is received)
    , client = net.connect({host:'example.com', port: 8124}); // connect to the server

client.on('data', function(chunk) {
    if (nread + chunk.length > buffer.length) { // if the buffer is too small to hold the data
        var need = Math.min(chunk.length, allocate); // allocate at least 4kB
        if (nread + need > max) throw new Error('Buffer overflow'); // uh-oh, we're all full - TODO you'll want to handle this more gracefully

        var newbuf = new Buffer(buffer.length + need); // because Buffers can't be resized, we must allocate a new one
        buffer.copy(newbuf); // and copy the old one's data to the new one
        buffer = newbuf; // the old, small buffer will be garbage collected
    }

    chunk.copy(buffer, nread); // copy the received chunk of data into the buffer
    nread += chunk.length; // add this chunk's length to the total number of bytes buffered

    pump(); // look at the buffer to see if we've received enough data to act
});

client.on('end', function() {
    // handle disconnect
});


client.on('error', function(err) {
    // handle errors
});


function find(byte) { // look for a specific byte in the buffer
    for (var i = nproc; i < nread; i++) { // look through the buffer, starting from where we left off last time
        if (buffer.readUInt8(i, true) == byte) { // we've found one
            return i;
        }
    }
}
function slice(bytes) { // discard bytes from the beginning of a buffer
    buffer = buffer.slice(bytes); // slice off the bytes
    nread -= bytes; // note that we've removed bytes
    nproc = 0; // and reset the processed bytes counter
}

function pump() {
    var pos; // position of a EOT character

    while ((pos = find(0x04)) >= 0) { // keep going while there's a EOT (0x04) somewhere in the buffer
        if (pos == 0) { // if there's more than one EOT in a row, the buffer will now start with a EOT
            slice(1); // discard it
            continue; // so that the next iteration will start with data
        }
        process(buffer.slice(0,pos)); // hand off the message
        slice(pos+1); // and slice the processed data off the buffer
    }
}

function process(msg) { // here's where we do something with a message
    if (msg.length > 0) { // ignore empty messages
        // here's where you have to decide what to do with the data you've received
        // experiment with the protocol
    }
}

Completely untested, so there's likely errors. The main thing to gather here is that as data arrives, you buffer it in memory. Once you find a delimiter character in your buffer, you can process the message.

josh3736
  • 139,160
  • 33
  • 216
  • 263
  • Buffer! Ok. That's a great start. I was actually looking at streams trying to figure it out. Buffer's crossed my attention somewhere but didn't stick. I'll take a closer look at them. Thanks! And another thanks for the quick reply :) – lee8oi Oct 08 '12 at 21:00
  • @user1287536: Yeah, [`Stream`](http://nodejs.org/api/stream.html) is a super class of things like network connections, file readers, crypto, and (de)compression. Their `data` event (usually) emits `Buffer`s. Also take a look at [this example of buffering a stream](http://stackoverflow.com/a/12467689/201952). In that answer, I'm buffering the output of gzip, but it would be very similar to buffer a network stream. – josh3736 Oct 08 '12 at 21:08
  • makes sense. I see your buffer code in there. Certainly gives me an idea on how to apply it to my code. Thanks! – lee8oi Oct 08 '12 at 21:20
  • Actually I might be back to square one. I'm examining the data returned in the 'data' event, I don't see anything but regular text. Nothing that resembles a salt or encryption. Buffer from what I can see is just a place to store up all the data chunks as they come in. I need to grab a piece of information from the stream itself somehow. The fgetc() function in php reads streams, not buffers. – lee8oi Oct 08 '12 at 22:11
  • ...or I'm probably just getting confused and need to get another perspective of things. – lee8oi Oct 08 '12 at 22:17
  • `fgetc` allows you to *fetch* data from a stream one character at a time; PHP buffers the incoming data for you. In Node, the stream *gives data to you* as it arrives (the `data` event), and you have to handle it. It most cases, this means buffering data until you have enough to process it. In the case of the login protocol you're trying to use, it looks like messages are delimited by 0x04. See the update for an example of how to buffer a protocol. – josh3736 Oct 08 '12 at 23:29
  • Right, ok. That's certainly the perspective I needed. And I do see your updated code example. Thanks a ton, I do realize it might be imperfect, but certainly a much better starting point. I'm starting to think I might just pull this off :O) – lee8oi Oct 08 '12 at 23:42
  • ENOENT means file or directory not found. Usually thrown by `fs` when you try to open a file that doesn't exist. – josh3736 Oct 08 '12 at 23:57
  • ok I've played with things and discovered the answer was much simpler than expected. I really felt like an idiot for overthinking things. @josh3736 you were right about the buffer, only the data event actually returned a buffer. I simply had to convert it toString() to use it. – lee8oi Oct 18 '12 at 13:02