2

我有一个 Ruby on Rails api,它处理一个简单的 API 调用并返回一些加密数据。加密是在 C++ 中使用 ruby​​ 本机 C api 完成的。(参考这里)。

本机部分在作为独立程序编译和链接时可以正常工作,并且在 IRB 中与 ruby​​ 一起使用时也可以正常工作。

但是,当我在 Rails API 中使用它时,有时会收到“堆栈级别太深”错误。

错误似乎发生或不发生取决于处理的数据的大小。

根据这个答案,堆栈'级别'实际上是堆栈空间,所以如果我有更多数据要处理,那么我在堆栈中有更多数据,所以它会更快地填满等等......

为了简单起见并避免忘记释放分配的内存,我最初将所有变量都留在了堆栈中。看到这个错误,我切换到了动态分配的方法。然而与我的预期相反,堆栈级别太深的错误发生在更小的数据大小。

数据控制器.rb

  def load_data data_path, width
    authorize!                                                                                                                                                               

    encrypted = NativeDataProtector.encrypt(data_path, get_key(), get_iv())
    return [ encrypted, "application/octet-stream" ]
  end

native_encryptor.cpp

VALUE encrypt_n(VALUE _self, VALUE data_path, VALUE key, VALUE salt){
  DataProtector protector;
  string *b64 = protector.encrypt(StringValueCStr(data_path),   \
                 StringValueCStr(key),   \
                 StringValueCStr(salt));

    VALUE ret = rb_str_new(b64->c_str(), b64->length());
    delete(b64);
  return ret;
}

extern "C" void Init_data_protector() {
  VALUE mod = rb_define_module("NativeDataProtector");
  rb_define_module_function(mod, "encrypt", (VALUE(*)(ANYARGS))encrypt_n, 3);
}

加密.h

#include <ruby.h>
#include "extconf.h"

#include <iostream>
#include <fstream>
#include <vector>
#include <list>

#include <openssl/conf.h>
#include <openssl/evp.h>
#include <openssl/err.h>

class DataProtector {

 private :
  int pad_cleartext(vector<unsigned char> *cleartext);
  vector<unsigned char> *read_data(string path);
  int aes_encrypt(vector<unsigned char> *plaintext, string key,
          string iv, unsigned char *ciphertext);
  string to_b64(unsigned char* in);
  void handleErrors(void);

 public :
  string *encrypt(string data_path, string key, string salt);
};

加密.cpp

string *DataProtector::encrypt(string data_path, string key, string salt) {
  vector<unsigned char> *cleartext = readData(data_path);
  int length = pad_cleartext(cleartext);

  unsigned char* output = new unsigned char[length + 16];

  int ciphertext_len;

  // encrypt
  string *encrypted = new string("");
  ciphertext_len = aes_encrypt(&((*cleartext), key, iv, output);
  (*encrypted) += to_b64(output);

  delete(cleartext);
  delete(output);

  return encrypted;
}

int DataProtector::aes_encrypt(vector<unsigned char> *plaintext, string key,
  string iv, unsigned char *ciphertext)
{
  EVP_CIPHER_CTX *ctx;

  int len;

  int ciphertext_len;

  /* Create and initialise the context */
  if(!(ctx = EVP_CIPHER_CTX_new())) handleErrors();

  /* Initialise the encryption operation. IMPORTANT - ensure you use a key
   * and IV size appropriate for your cipher
   * In this example we are using 256 bit AES (i.e. a 256 bit key). The
   * IV size for *most* modes is the same as the block size. For AES this
   * is 128 bits */
  if(1 != EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, (const unsigned char *)key.c_str(), (const unsigned char *)iv.c_str()))
    handleErrors();

  /* Provide the message to be encrypted, and obtain the encrypted output.
   * EVP_EncryptUpdate can be called multiple times if necessary
   */
  if(1 != EVP_EncryptUpdate(ctx, ciphertext, &len, reinterpret_cast<unsigned char*>(plaintext->data()), plaintext->size()))
    handleErrors();
  ciphertext_len = len;

  /* Finalise the encryption. Further ciphertext bytes may be written at
   * this stage.
   */
  if(1 != EVP_EncryptFinal_ex(ctx, ciphertext + len, &len)) handleErrors();
  ciphertext_len += len;

  /* Clean up */
  EVP_CIPHER_CTX_free(ctx);

  return ciphertext_len;
}

int DataProtector::pad_cleartext(vector<unsigned char> *in) {
  // padds to length multiple of 16
  int nb_blocks = in->size() / 16 + ((in->size()%16 == 0)? 1:1);
  int size = nb_blocks*16;

  for (unsigned int i=in->size(); i<size; i++) {
    unsigned char c = '0';
    in->push_back(c);
  }
  return size;
}

vector<unsigned char> *DataProtector::read_data(string path) {
    streampos size;
    ifstream file(path, ios::binary);

    file.seekg(0, ios::end);
    size = file.tellg();
    file.seekg(0, ios::beg);

    vector<unsigned char> *data = new vector<unsigned char>(fileSize);
    file.read((char*) &data[0], size);
    return data;
}

void DataProtector::handleErrors(void) {
  ERR_print_errors_fp(stderr);
  abort();
}

(实际的加密来自这里

我得到的错误堆栈跟踪:

SystemStackError (stack level too deep):

app/controllers/data_controller.rb:41:in `encrypt'
app/controllers/data_controller.rb:41:in `load_data'
app/controllers/data_controller.rb:15:in `show'

相信这个错误的原因是堆栈上分配的数据过多,而不是递归问题。但是,我不明白为什么切换到堆分配并没有改善任何东西。

我可以想象2个解决方案:

  1. 在 ruby​​ 中切割数据并用更少的数据多次调用本机方法。
  2. 增加红宝石堆栈大小。但是,对于我的项目,对于性能/资源问题,这两种解决方案都不理想。

有没有其他方法可以减少程序对堆栈的使用?

4

0 回答 0