4

I have written a simple TCP server on node.js to send some data to a Chrome app. In the chrome app, when I get the data, I convert that to string using below function, I get an exception "byte length of Uint16Array should be a multiple of 2"

String.fromCharCode.apply(null, new Uint16Array(buffer))

I could not find any information about what could be causing this and how to fix this. Any pointers on this is highly appreciated.

Below is the code in node.js server for sending the data to client:

socket.on('data', function(data) {

    console.log('DATA ' + socket.remoteAddress + ': ' + data);
    // Write the data back to the socket, 
    //   the client will receive it as data from the server
    var r= socket.write('from server\r\n');

});

Below is the code from chrome app:

  chrome.sockets.tcp.onReceive.addListener(function (info) {
            console.log('onListener registered');
            if (info.socketId != socketid)
                return;
            else {
                try {

                   data = ab2str(info.data);
                    console.log(data);
                }
                catch (e) {
                    console.log(e);
                }

            }
            // info.data is an arrayBuffer.
        });

 function ab2str(buf) {
    return String.fromCharCode.apply(null, new Uint16Array(buf));
}
Xan
  • 74,770
  • 16
  • 179
  • 206
Kapil
  • 9,469
  • 10
  • 40
  • 53
  • can you show more of how you are getting buffer? – Victory Nov 05 '14 at 10:30
  • In general, [this question](https://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers) is canonical. I'm not going to dupehammer, though. – Xan Jul 11 '16 at 12:28

4 Answers4

13

The modern (Chrome 38+) way to do this would be, assuming the encoding is UTF-8:

var decoder = new TextDecoder("utf-8");

function arrayBufferToString(buffer) {
    return decoder.decode(new Uint8Array(buffer));
}

This uses the TextDecoder API; see documentation for more options, such as a different encoding.

See also: Easier ArrayBuffer<->String conversion with the Encoding API @ Google Developers

mikemaccana
  • 110,530
  • 99
  • 389
  • 494
Xan
  • 74,770
  • 16
  • 179
  • 206
  • 1
    There is a [TextDecoder shim/polyfill](https://github.com/inexorabletash/text-encoding) for other browsers which do not support this API. – cuixiping Jul 15 '16 at 16:53
9

You're probably seeing this problem because your app has received an odd number of bytes on the socket, but you're trying to create an array of 2-byte-wide items out of it (because that's what fits into a Uint16Array)

If your app receives the string "Hello" over the network (5 bytes), then you can cast that to a Uint8Array, and it will look like this:

Item:        0   1   2   3   4
Char:        H   e   l   l   o
Uint8 Value: 72  101 108 108 111

casting it to an Uint16Array, though will try to do this:

Item   0     1     2
Chars  He    ll    o?
IntVal 25928 27756 ?????

Without a 6th byte to work with, it can't construct the array, and so you get an exception.

Using a Uint16Array for the data only makes sense if you are expecting UCS-2 string data on the socket. If you are receiving plain ASCII data, then you want to cast that to a Uint8Array instead, and map String.fromCharCode on that. If it's something else, such as UTF-8, then you'll have to do some other conversion.

No matter what, though, the socket layer is always free to send you data in chunks of any length. Your app will have to deal with odd sizes, and save any remainder that you can't deal with right away, so that you can use it when you receive the next chunk of data.

Ian Clelland
  • 43,011
  • 8
  • 86
  • 87
  • It depends on the app -- you might want to use a Uint8Array instead, or like I said in the last paragraph, buffer any trailing byte and then use it as the first byte when you get the next packet of data. – Ian Clelland Jun 09 '16 at 03:44
  • My problem was that I had an arraybuffer responsetype that was returning an error, but part of the text inside the error contained chars with accents and they were being decoded as garbage, almost unreadable chars. I tried to decode it with Uint16Array like OP did but got the same error. I ended up finding this: https://gist.github.com/boushley/5471599 and I was able to decode the string correctly without need to use Uint16Array. – CesarD Jun 09 '16 at 14:16
2

Kind of old and late, but perhaps using this function (original source) works better (it worked for me for decoding arraybuffer to string without leaving some special chars as total garbage):

function decodeUtf8(arrayBuffer) {
  var result = "";
  var i = 0;
  var c = 0;
  var c1 = 0;
  var c2 = 0;

  var data = new Uint8Array(arrayBuffer);

  // If we have a BOM skip it
  if (data.length >= 3 && data[0] === 0xef && data[1] === 0xbb && data[2] === 0xbf) {
    i = 3;
  }

  while (i < data.length) {
    c = data[i];

    if (c < 128) {
      result += String.fromCharCode(c);
      i++;
    } else if (c > 191 && c < 224) {
      if( i+1 >= data.length ) {
        throw "UTF-8 Decode failed. Two byte character was truncated.";
      }
      c2 = data[i+1];
      result += String.fromCharCode( ((c&31)<<6) | (c2&63) );
      i += 2;
    } else {
      if (i+2 >= data.length) {
        throw "UTF-8 Decode failed. Multi byte character was truncated.";
      }
      c2 = data[i+1];
      c3 = data[i+2];
      result += String.fromCharCode( ((c&15)<<12) | ((c2&63)<<6) | (c3&63) );
      i += 3;
    }
  }
  return result;
}
CesarD
  • 573
  • 14
  • 30
0

There is an asynchronous way using Blob and FileReader.

You can specify any valid encoding.

function arrayBufferToString( buffer, encoding, callback ) {
    var blob = new Blob([buffer],{type:'text/plain'});
    var reader = new FileReader();
    reader.onload = function(evt){callback(evt.target.result);};
    reader.readAsText(blob, encoding);
}

//example:
var buf = new Uint8Array([65,66,67]);
arrayBufferToString(buf, 'UTF-8', console.log.bind(console)); //"ABC"
cuixiping
  • 24,167
  • 8
  • 82
  • 93
  • You may want to look into [`TextDecoder` API](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) for a synchronous way to do it. – Xan Jul 11 '16 at 12:27
  • Thanks. [TextDecoder](https://developer.mozilla.org/en-US/docs/Web/API/TextDecoder) is very good but is only supported by firefox and chrome. A big shim is required for IE and Safari. – cuixiping Jul 14 '16 at 07:19
  • The question is about a Chrome App, so runtime is explicitly Chrome. – Xan Jul 14 '16 at 07:56