Jump To …

decode.js

The parser is based on node-binary.

var binary = require("binary");

Welcome to the decoding module. Here you'll find all manner of decode related functionality. Feel free to clarify any comments here.

module.exports = Decode;

This is where you'll want to start. The usage is usually like this:

nibbit.decode(some_buffer, function(err, ast) { ... });

If the first element isn't a compound type (mentioned as being mandatory on the wiki, it'll throw an error. In other error cases, it'll probably just throw exceptions and/or set fire to your computer. It's not very well tested with bad input.

This file could really do with more documentation on how the stuff works internally.

function Decode(data, done) {
  b = binary(data);

  b.word8("type").tap(function(vars) {
    var type = vars.type;
    delete vars.type;

    if (type != 10) {
      return done(Error("first tag must be compound"));
    }

    Decode[type](b, true).tap(function(vars) {
      return done(null, vars.data);
    });
  });
};

Decode.byte = Decode[1] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word8s("data");

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [1, name, vars.data];
  });

  return b;
};

Decode.short = Decode[2] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word16bs("data");

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [2, name, vars.data];
  });

  return b;
};

Decode.integer = Decode[3] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word32bs("data");

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [3, name, vars.data];
  });

  return b;
};

Decode.long = Decode[4] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word64bs("data");

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [4, name, vars.data];
  });

  return b;
};

Decode.float = Decode[5] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.buffer("data", 4);

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [5, name, vars.data.readFloatBE(0)];
  });

  return b;
};

Decode.double = Decode[6] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.buffer("data", 8);

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [6, name, vars.data.readDoubleBE(0)];
  });

  return b;
};

Decode.byte_array = Decode[7] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word32bs("length").buffer("data", "length");

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [7, name, Array.prototype.slice.apply(vars.data)];
    delete vars.length;
  });

  return b;
};

Decode.string = Decode[8] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word16be("data_length").buffer("data", "data_length");

  b.tap(function(vars) {
    var name = null;

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    vars.data = [8, name, vars.data.toString()];
    delete vars.data_length;
  });

  return b;
};

Decode.list = Decode[9] = function(b, has_name) {
  if (has_name) {
    b.word16be("name_length").buffer("name", "name_length");
  }

  b.word8s("type").word32bs("count");

  b.tap(function(vars) {
    var name = null, type = null, count = 0, elements = [];

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    type = vars.type;
    count = vars.count;

    delete vars.type;
    delete vars.count;

    var i = 0;
    b.loop(function(end) {
      if (i++ >= count) {
        return end();
      }

      Decode[type](this, false).tap(function(vars) {
        elements.push(vars.data);
        delete vars.data;
      });
    }).tap(function(vars) {
      vars.data = [9, name, elements];
    });
  });

  return b;
};

Decode.compound = Decode[10] = function(b, has_name) {
  if (has_name) {
    b.word16be("name_length").buffer("name", "name_length");
  }

  b.tap(function(vars) {
    var name = null, elements = [];

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
    }

    this.loop(function(end, vars) {
      this.word8("type").tap(function(vars) {
        var type = vars.type;
        delete vars.type;

        if (type === 0 || type === null) {
          return end();
        }

        Decode[type](this, true).tap(function(vars) {
          elements.push(vars.data);
          delete vars.data;
        });
      });
    }).tap(function(vars) {
      vars.data = [10, name, elements];
    });
  });

  return b;
};

Decode.integer_array = Decode[11] = function(b, has_name) {
  if (has_name) {
    b.word16bs("name_length").buffer("name", "name_length");
  }

  b.word32bs("count");

  b.tap(function(vars) {
    var name = null, count = 0, elements = [];

    if (vars.name_length && vars.name) {
      name = vars.name.toString();
      delete vars.name_length;
      delete vars.name;
    }

    count = vars.count;
    delete vars.count;

    var i = 0;
    this.loop(function(end) {
      if (i++ >= count) {
        return end();
      }

      Decode.integer(this).tap(function(vars) {
        elements.push(vars.data);
        delete vars.data;
      });
    }).tap(function(vars) {
      vars.data = [11, name, elements];
    });
  });

  return b;
};