text: //     telegram-mt-node
  //     Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
  //     Released under the MIT License
  //     https://github.com/enricostara/telegram-mt-node
  
  //     EncryptedMessage class
  //
  // This class provide a encrypted-text message for data transmission in MTProto
  
  // Import dependencies
  var util = require('util');
  var mtproto = require('../mtproto');
  var utility = require('../utility');
  var security = require('../security');
  var tl = require('telegram-tl-node');
  var logger = require('get-log')('message.EncryptedMessage');
  
  EncryptedMessage.InnerMessage = new tl.TypeBuilder.buildType('mtproto.type', {
      "predicate": "InnerMessage",
      "params": [
          {
              "name": "server_salt",
              "type": "long"
          }, {
              "name": "session_id",
              "type": "long"
          }, {
              "name": "payload",
              "type": "%Message"
          }
      ],
      "type": "InnerMessage"
  });
  
  // To get an instance for `serialization`:
  //
  //     new EncryptedMessage({message: myMessageBuffer});
  // Provide the payload as `Buffer`:
  //
  // To get an instance for `de-serialization`:
  //
  //     new EncryptedMessage({buffer: myBuffer});
  // Provide a `buffer` containing the plain message from which extract the payload
  function EncryptedMessage(options) {
      var opts = options ? options : {};
      this.constructor.super_.call(this, opts.buffer, opts.offset);
      this.authKey = opts.authKey;
      if (opts.message) {
          if (util.isArray(opts.message)) {
              var messages = [];
              for (var i = 0; i < opts.message.length; i++) {
                  var message = opts.message[i];
                  var isContentRelated = (message.getTypeName().slice(0, 8) != 'mtproto.' || message.getTypeName().slice(-15) == 'invokeWithLayer') ;
                  if (logger.isDebugEnabled()) {
                      logger.debug('Message [%s] is content related? %s', message.getTypeName(), isContentRelated);
                  }
                  messages.push(wrap(message, opts.sequenceNumber, isContentRelated));
              }
              this.body = new mtproto.type.Msg_container({
                  props: {
                      messages: new tl.TypeVector({module: 'mtproto.type', type: '%Message', list: messages})
                  }
              });
              this.innerMessage = createInnerMessage(this.body, opts, false);
          } else {
              this.body = opts.message;
              this.innerMessage = createInnerMessage(this.body, opts, true);
          }
          this.messageId = this.innerMessage.payload.msg_id;
      }
  }
  function createInnerMessage(body, opts, isContentRelated) {
      return new EncryptedMessage.InnerMessage({
          props: {
              server_salt: opts.serverSalt,
              session_id: opts.sessionId,
              payload: wrap(body, opts.sequenceNumber, isContentRelated)
          }
      });
  }
  function wrap(body, sequenceNumber, isContentRelated) {
      return new mtproto.type.Message({
          props: {
              msg_id: utility.createMessageId(),
              seqno: sequenceNumber.generate(isContentRelated),
              bytes: body.length,
              body: body
          }
      });
  }
  
  // Extend TypeObject class
  util.inherits(EncryptedMessage, tl.TypeObject);
  
  // This method serialize the EncryptedMessage
  EncryptedMessage.prototype.serialize = function () {
      if (!this.innerMessage || !this.authKey) {
          logger.warn('Unable to serialize the message, "message" & "authKey" are mandatory!');
          return false;
      }
      // Serialize the message
      this.decryptedData = this.innerMessage.serialize();
      // Create the message_key
      this.msgKey = utility.createSHAHash(this.decryptedData).slice(4, 20);
      // Derivate the AES key
      this.aes = this.authKey.derivateAesKey(this.msgKey);
      if (logger.isDebugEnabled()) {
          logger.debug('Before encryption: %s', this.toPrintable({authKey: true}));
      }
      // Start serializing..
      this.writeLong(this.authKey.id);
      this.writeInt128(this.msgKey);
      // Encrypt the message
      this.encryptedData = security.cipher.aesEncrypt(this.decryptedData, this.aes.key, this.aes.iv);
      // Call the low level write method
      this._writeBytes(this.encryptedData);
      if (logger.isDebugEnabled()) {
          logger.debug('After encryption: %s', this.toPrintable({authKey: true}));
      }
      return this.retrieveBuffer();
  };
  
  // This method de-serialize the EncryptedMessage
  EncryptedMessage.prototype.deserialize = function (isSymmetric) {
      if (!this.isReadonly() || !this.authKey) {
          var msg = 'Unable to deserialize the message, "message" is not readonly || "authKey" is undefined!';
          logger.warn(msg);
          throw new Error(msg);
      }
      var msgAuthKeyId = this.readLong();
      if (msgAuthKeyId !== tl.utility.buffer2StringValue(this.authKey.id)) {
          msg = 'Unable to decrypt the message, the authKey ids do not match!';
          logger.warn(msg);
          throw new Error(msg);
      }
      // Call low level read method
      this.msgKey = this._readBytes(16);
      this.aes = this.authKey.derivateAesKey(this.msgKey, !isSymmetric);
      // Call low level read method
      this.encryptedData = this._readBytes();
      // Decrypt the message
      if (logger.isDebugEnabled()) {
          logger.debug('Before decryption: %s', this.toPrintable({authKey: true}));
      }
      this.decryptedData = security.cipher.aesDecrypt(this.encryptedData, this.aes.key, this.aes.iv);
      if (logger.isDebugEnabled()) {
          logger.debug('After decryption: %s', this.toPrintable({authKey: true}));
      }
      // Deserialize the message
      this.innerMessage = new EncryptedMessage.InnerMessage({buffer: this.decryptedData}).deserialize({noLengthCheck: true});
      this.body = this.innerMessage.payload.body;
      this.serverSalt = this.innerMessage.server_salt;
      this.sessionId = this.innerMessage.session_id;
      this.messageId = this.innerMessage.payload.msg_id;
      return this;
  };
  
  // Export the class
  module.exports = exports = EncryptedMessage;
  