<commit_before>/* Gobby - GTK-based collaborative text editor
 * Copyright (C) 2008, 2009 Armin Burgmeier <armin@arbur.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "features.hpp"
#include "window.hpp"

#include "commands/file-tasks/task-open-multiple.hpp"
#include "core/docwindow.hpp"
#include "core/iconmanager.hpp"
#include "core/noteplugin.hpp"
#include "core/closableframe.hpp"

#include "util/i18n.hpp"

#include <gtkmm/frame.h>

Gobby::Window::Window(const IconManager& icon_mgr,
	                    Config& config,
	                    UniqueApp* app):
	Gtk::Window(Gtk::WINDOW_TOPLEVEL), m_config(config),
	m_lang_manager(gtk_source_language_manager_get_default()),
	m_preferences(m_config), m_icon_mgr(icon_mgr), m_app(app),
	m_header(m_preferences, m_lang_manager),
	m_browser(*this, Plugins::TEXT, m_statusbar, m_preferences),
	m_folder(m_preferences, m_lang_manager),
	m_statusbar(m_folder, m_preferences),
	m_info_storage(INF_GTK_BROWSER_MODEL(m_browser.get_store())),
	m_operations(m_info_storage, m_statusbar),
	m_commands_autosave(m_folder, m_operations, m_info_storage,
	                    m_preferences),
	m_commands_browser(m_browser, m_folder, m_info_storage, m_statusbar,
	                   m_preferences),
	m_commands_browser_context(*this, m_browser, m_file_chooser,
	                           m_operations, m_preferences),
	m_commands_folder(m_folder),
	m_commands_file(*this, m_header, m_browser, m_folder, m_statusbar,
	                m_file_chooser, m_operations, m_info_storage,
	                m_preferences),
	m_commands_edit(*this, m_header, m_folder, m_statusbar,
	                m_preferences),
	m_commands_view(m_header, m_folder, m_preferences),
	m_commands_help(*this, m_header, m_icon_mgr),
	m_title_bar(*this, m_folder)
{
	g_object_ref(app);

	unique_app_watch_window(app, gobj());
	g_signal_connect(app, "message-received",
	                 G_CALLBACK(on_message_received_static), this);

	m_header.show();
	m_browser.show();
	m_folder.show();

	// Build UI
	add_accel_group(m_header.get_accel_group() );

	Gtk::Frame* frame_browser = Gtk::manage(new ClosableFrame(
		_("Document Browser"), IconManager::STOCK_DOCLIST,
		m_preferences.appearance.show_browser));
	frame_browser->set_shadow_type(Gtk::SHADOW_IN);
	frame_browser->add(m_browser);
	// frame_browser manages visibility itself

	Gtk::Frame* frame_text = Gtk::manage(new Gtk::Frame);
	frame_text->set_shadow_type(Gtk::SHADOW_IN);
	frame_text->add(m_folder);
	frame_text->show();

	m_paned.pack1(*frame_browser, false, false);
	m_paned.pack2(*frame_text, true, false);
	m_paned.show();

	m_mainbox.pack_start(m_header, Gtk::PACK_SHRINK);
	m_mainbox.pack_start(m_paned, Gtk::PACK_EXPAND_WIDGET);
	m_mainbox.pack_start(m_statusbar, Gtk::PACK_SHRINK);
	m_mainbox.show();

	// Give initial focus to the browser, which will in turn give focus
	// to the "Direct Connection" expander, so people can quickly
	// get going.
	set_focus_child(m_browser);
	add(m_mainbox);

	set_default_size(800, 600);
	set_role("Gobby");
}

Gobby::Window::~Window()
{
	// Serialise preferences into config
	m_preferences.serialize(m_config);
	g_object_unref(m_app);
}

bool Gobby::Window::on_delete_event(GdkEventAny* event)
{
#if 0
	if(m_buffer.get() == NULL) return false;
	if(!m_buffer->is_open() ) return false;

	Gtk::MessageDialog dlg(
		*this,
		_("You are still connected to a session"),
		false,
		Gtk::MESSAGE_WARNING,
		Gtk::BUTTONS_NONE,
		true
	);

	dlg.set_secondary_text(
		_("Do you want to close Gobby nevertheless?")
	);

	Gtk::Image* img = Gtk::manage(new Gtk::Image(Gtk::Stock::CANCEL,
	                                             Gtk::ICON_SIZE_BUTTON));
	Gtk::Button* cancel_button
		= dlg.add_button(_("C_ancel"), Gtk::RESPONSE_CANCEL);
	dlg.add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_YES);
	cancel_button->set_image(*img);
	cancel_button->grab_focus();

	return dlg.run() != Gtk::RESPONSE_YES;
#endif
	return false;
}

// GtkWindow catches keybindings for the menu items _before_ passing them to
// the focused widget. This is unfortunate and means that pressing ctrl+V
// in an entry on the browser ends up pasting text in the TextView.
// Here we override GtkWindow's handler to do the same things that it
// does, but in the opposite order and then we chain up to the grand
// parent handler, skipping Gtk::Window::key_press_event().
// This code is basically stolen from gedit, but ported to C++.
bool Gobby::Window::on_key_press_event(GdkEventKey* event)
{
	// We can't let GtkSourceView handle this, since we override
	// Undo/Redo. TODO: This is a bit of a hack. A proper solution would
	// perhaps be to subclass GtkSourceView, and either
	// unregister/reregister the keybinding there, or making sure the
	// key-press-event default handler returns false.
	if(event->keyval == GDK_z || event->keyval == GDK_Z)
		return Gtk::Window::on_key_press_event(event);

	bool handled = gtk_window_propagate_key_event(gobj(), event);
	if(!handled) handled = gtk_window_activate_key(gobj(), event);

	// Skip Gtk::Window default handler here:
	if(!handled) handled = Gtk::Container::on_key_press_event(event);

	return handled;
}

void Gobby::Window::on_realize()
{
	Gtk::Window::on_realize();

	m_paned.set_position(m_paned.get_width() * 2 / 5);
}

void Gobby::Window::on_show()
{
	Gtk::Window::on_show();

	if(!m_config.get_root()["initial"].get_value<bool>("run", false))
	{
		m_initial_dlg.reset(new InitialDialog(*this, m_preferences,
		                                      m_icon_mgr));
		m_initial_dlg->present();
		m_initial_dlg->signal_hide().connect(
			sigc::mem_fun(*this,
			              &Window::on_initial_dialog_hide));
	}
}

void Gobby::Window::on_initial_dialog_hide()
{
	m_initial_dlg.reset(NULL);
	// Don't show again
	m_config.get_root()["initial"].set_value("run", true);
}

UniqueResponse Gobby::Window::on_message_received(UniqueCommand command,
                                                  UniqueMessageData* message,
                                                  guint time)
try {
	UniqueResponse res;
	switch (command) {
	case UNIQUE_ACTIVATE:
		gtk_window_set_screen(gobj(),
													unique_message_data_get_screen(message));
		present(time);
		return UNIQUE_RESPONSE_OK;
	case UNIQUE_OPEN:
		{
			gchar** uris = unique_message_data_get_uris(message);
			if (!uris)
				return UNIQUE_RESPONSE_FAIL;
			TaskOpenMultiple* task = new TaskOpenMultiple(m_commands_file);
			m_commands_file.set_task(task);
			for (const gchar* const* p = uris; *p; ++p)
				task->add_file(Gio::File::create_for_uri(*p));
			g_strfreev(uris);
			return UNIQUE_RESPONSE_OK;
		}
	default:
		return UNIQUE_RESPONSE_PASSTHROUGH;
	}
} catch (...) {
  g_assert_not_reached();
}
<commit_msg>uniqueapp command handler: return/unused variable<commit_after>/* Gobby - GTK-based collaborative text editor
 * Copyright (C) 2008, 2009 Armin Burgmeier <armin@arbur.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the Free
 * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "features.hpp"
#include "window.hpp"

#include "commands/file-tasks/task-open-multiple.hpp"
#include "core/docwindow.hpp"
#include "core/iconmanager.hpp"
#include "core/noteplugin.hpp"
#include "core/closableframe.hpp"

#include "util/i18n.hpp"

#include <gtkmm/frame.h>

Gobby::Window::Window(const IconManager& icon_mgr,
	                    Config& config,
	                    UniqueApp* app):
	Gtk::Window(Gtk::WINDOW_TOPLEVEL), m_config(config),
	m_lang_manager(gtk_source_language_manager_get_default()),
	m_preferences(m_config), m_icon_mgr(icon_mgr), m_app(app),
	m_header(m_preferences, m_lang_manager),
	m_browser(*this, Plugins::TEXT, m_statusbar, m_preferences),
	m_folder(m_preferences, m_lang_manager),
	m_statusbar(m_folder, m_preferences),
	m_info_storage(INF_GTK_BROWSER_MODEL(m_browser.get_store())),
	m_operations(m_info_storage, m_statusbar),
	m_commands_autosave(m_folder, m_operations, m_info_storage,
	                    m_preferences),
	m_commands_browser(m_browser, m_folder, m_info_storage, m_statusbar,
	                   m_preferences),
	m_commands_browser_context(*this, m_browser, m_file_chooser,
	                           m_operations, m_preferences),
	m_commands_folder(m_folder),
	m_commands_file(*this, m_header, m_browser, m_folder, m_statusbar,
	                m_file_chooser, m_operations, m_info_storage,
	                m_preferences),
	m_commands_edit(*this, m_header, m_folder, m_statusbar,
	                m_preferences),
	m_commands_view(m_header, m_folder, m_preferences),
	m_commands_help(*this, m_header, m_icon_mgr),
	m_title_bar(*this, m_folder)
{
	g_object_ref(app);

	unique_app_watch_window(app, gobj());
	g_signal_connect(app, "message-received",
	                 G_CALLBACK(on_message_received_static), this);

	m_header.show();
	m_browser.show();
	m_folder.show();

	// Build UI
	add_accel_group(m_header.get_accel_group() );

	Gtk::Frame* frame_browser = Gtk::manage(new ClosableFrame(
		_("Document Browser"), IconManager::STOCK_DOCLIST,
		m_preferences.appearance.show_browser));
	frame_browser->set_shadow_type(Gtk::SHADOW_IN);
	frame_browser->add(m_browser);
	// frame_browser manages visibility itself

	Gtk::Frame* frame_text = Gtk::manage(new Gtk::Frame);
	frame_text->set_shadow_type(Gtk::SHADOW_IN);
	frame_text->add(m_folder);
	frame_text->show();

	m_paned.pack1(*frame_browser, false, false);
	m_paned.pack2(*frame_text, true, false);
	m_paned.show();

	m_mainbox.pack_start(m_header, Gtk::PACK_SHRINK);
	m_mainbox.pack_start(m_paned, Gtk::PACK_EXPAND_WIDGET);
	m_mainbox.pack_start(m_statusbar, Gtk::PACK_SHRINK);
	m_mainbox.show();

	// Give initial focus to the browser, which will in turn give focus
	// to the "Direct Connection" expander, so people can quickly
	// get going.
	set_focus_child(m_browser);
	add(m_mainbox);

	set_default_size(800, 600);
	set_role("Gobby");
}

Gobby::Window::~Window()
{
	// Serialise preferences into config
	m_preferences.serialize(m_config);
	g_object_unref(m_app);
}

bool Gobby::Window::on_delete_event(GdkEventAny* event)
{
#if 0
	if(m_buffer.get() == NULL) return false;
	if(!m_buffer->is_open() ) return false;

	Gtk::MessageDialog dlg(
		*this,
		_("You are still connected to a session"),
		false,
		Gtk::MESSAGE_WARNING,
		Gtk::BUTTONS_NONE,
		true
	);

	dlg.set_secondary_text(
		_("Do you want to close Gobby nevertheless?")
	);

	Gtk::Image* img = Gtk::manage(new Gtk::Image(Gtk::Stock::CANCEL,
	                                             Gtk::ICON_SIZE_BUTTON));
	Gtk::Button* cancel_button
		= dlg.add_button(_("C_ancel"), Gtk::RESPONSE_CANCEL);
	dlg.add_button(Gtk::Stock::CLOSE, Gtk::RESPONSE_YES);
	cancel_button->set_image(*img);
	cancel_button->grab_focus();

	return dlg.run() != Gtk::RESPONSE_YES;
#endif
	return false;
}

// GtkWindow catches keybindings for the menu items _before_ passing them to
// the focused widget. This is unfortunate and means that pressing ctrl+V
// in an entry on the browser ends up pasting text in the TextView.
// Here we override GtkWindow's handler to do the same things that it
// does, but in the opposite order and then we chain up to the grand
// parent handler, skipping Gtk::Window::key_press_event().
// This code is basically stolen from gedit, but ported to C++.
bool Gobby::Window::on_key_press_event(GdkEventKey* event)
{
	// We can't let GtkSourceView handle this, since we override
	// Undo/Redo. TODO: This is a bit of a hack. A proper solution would
	// perhaps be to subclass GtkSourceView, and either
	// unregister/reregister the keybinding there, or making sure the
	// key-press-event default handler returns false.
	if(event->keyval == GDK_z || event->keyval == GDK_Z)
		return Gtk::Window::on_key_press_event(event);

	bool handled = gtk_window_propagate_key_event(gobj(), event);
	if(!handled) handled = gtk_window_activate_key(gobj(), event);

	// Skip Gtk::Window default handler here:
	if(!handled) handled = Gtk::Container::on_key_press_event(event);

	return handled;
}

void Gobby::Window::on_realize()
{
	Gtk::Window::on_realize();

	m_paned.set_position(m_paned.get_width() * 2 / 5);
}

void Gobby::Window::on_show()
{
	Gtk::Window::on_show();

	if(!m_config.get_root()["initial"].get_value<bool>("run", false))
	{
		m_initial_dlg.reset(new InitialDialog(*this, m_preferences,
		                                      m_icon_mgr));
		m_initial_dlg->present();
		m_initial_dlg->signal_hide().connect(
			sigc::mem_fun(*this,
			              &Window::on_initial_dialog_hide));
	}
}

void Gobby::Window::on_initial_dialog_hide()
{
	m_initial_dlg.reset(NULL);
	// Don't show again
	m_config.get_root()["initial"].set_value("run", true);
}

UniqueResponse Gobby::Window::on_message_received(UniqueCommand command,
                                                  UniqueMessageData* message,
                                                  guint time)
try {
	switch (command) {
	case UNIQUE_ACTIVATE:
		gtk_window_set_screen(gobj(),
													unique_message_data_get_screen(message));
		present(time);
		return UNIQUE_RESPONSE_OK;
	case UNIQUE_OPEN:
		{
			gchar** uris = unique_message_data_get_uris(message);
			if (!uris)
				return UNIQUE_RESPONSE_FAIL;
			TaskOpenMultiple* task = new TaskOpenMultiple(m_commands_file);
			m_commands_file.set_task(task);
			for (const gchar* const* p = uris; *p; ++p)
				task->add_file(Gio::File::create_for_uri(*p));
			g_strfreev(uris);
			return UNIQUE_RESPONSE_OK;
		}
	default:
		return UNIQUE_RESPONSE_PASSTHROUGH;
	}
} catch (...) {
	g_assert_not_reached();
	return UNIQUE_RESPONSE_FAIL;
}
<|endoftext|>


<commit_before>/*
 * See ffx.h for details.
 *
 * References:
 *   [FFX2] http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ffx/ffx-spec2.pdf
 */

#include "ffx/ffx.h"

#include <math.h>

#include <string>

#include "third_party/aes/aes.h"

namespace ffx {

static bool ValidateKey(const std::string & key) {
  if (key.length() != kFfxKeyLengthInNibbles) {
    return false;
  }
  for (uint32_t i = 0; i < key.length(); ++i) {
    if (isxdigit(key[i])) {
      continue;
    } else {
      return false;
    }
  }
  return true;
}

/*
 * This function has an unfortunate number of input parameters.
 * However, this is a direct implementation of the round-function algorithm
 * F_K(n,T,i,B) defined in [FFX2].
 */
static bool RoundFunction(const std::string & K,
                          uint32_t n,
                          const mpz_class & tweak,
                          uint32_t tweak_len_in_bits,
                          uint32_t i,
                          const mpz_class & B,
                          uint32_t B_len,
                          mpz_class * retval) {

  // [FFX2] pg 3., line 31
  uint32_t vers = 1;
  uint32_t t = ceil(tweak_len_in_bits / 8.0);
  uint32_t beta = ceil(n / 2.0);
  uint32_t b = ceil(beta / 8.0);
  uint32_t d = 4 * ceil(b / 4.0);

  // [FFX2] pg 3., line 32
  uint32_t m = 0;
  if ((i & 1) == 0) {
    m = n / 2;
  } else {
    m = ceil(n / 2.0);
  }

  // [FFX2] pg 3., line 33
  mpz_class P = 0;
  uint32_t P_len = (1 + 1 + 1 + 3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(vers)             << (1 + 1 + 3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(2)                << (1 + 3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(1)                << (3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(2)                << (1 + 1 + 4 + 4) * 8;
  P += mpz_class(10)               << (1 + 4 + 4) * 8;
  P += mpz_class(n / 2)            << (4 + 4) * 8;
  P += mpz_class(n)                << (4) * 8;
  P += t;

  uint32_t B_bits = b * 8;

  // [FFX2] pg 3., line 34
  mpz_class Q = 0;
  uint32_t T_offset = ((((-1 * t) - b - 1) % 16) * 8);
  T_offset += 8;
  T_offset += B_bits;

  uint32_t Q_len = tweak_len_in_bits + T_offset;
  Q += mpz_class(tweak) << T_offset;
  Q += mpz_class(i) << B_bits;
  Q += B;

  mpz_class Y = 0;

  // [FFX2] pg 3., line 35
  bool cbc_success = AesCbcMac(K, (P << Q_len) + Q, P_len + Q_len, &Y);
  if (!cbc_success) {
    return false;
  }

  // [FFX2] pg 3., line 36
  mpz_class Z = Y;
  uint32_t Z_len = 16;
  mpz_class counter = 1;
  while (Z_len < (d + 4)) {
    mpz_class ctxt;
    AesEcbEncrypt(K, (Y + counter), 128, &ctxt);
    Z_len += 16;
    Z = Z << 128;
    Z += ctxt;
    ++counter;
  }

  // [FFX2] pg 3., line 37
  BitMask(Z, Z_len * 8, 0, ((d + 4) * 8) - 1, &Y);

  // [FFX2] pg 3., line 3=8
  mpz_class modulus = 0;
  mpz_ui_pow_ui(modulus.get_mpz_t(), 2, m);
  Y = Y % modulus;

  // [FFX2] pg 3., line 39
  *retval = Y;
}

bool Ffx::Encrypt(const std::string & key ,
                  const mpz_class & tweak,
                  uint32_t tweak_len_in_bits,
                  const mpz_class & plaintext,
                  uint32_t plaintext_len_in_bits,
                  mpz_class * ciphertext) {

  // [FFX2] pg 3., line 11
  if (!ValidateKey(key)) {
    return false;
  }

  mpz_class & retval = *ciphertext;

  // [FFX2] pg 3., line 14-15
  uint32_t n = plaintext_len_in_bits;
  uint32_t l = plaintext_len_in_bits / 2;
  uint32_t r = kDefaultFfxRounds;
  mpz_class A, B;
  BitMask(plaintext, plaintext_len_in_bits, 0, l - 1, &A);
  BitMask(plaintext, plaintext_len_in_bits, l, n - 1, &B);
  uint32_t B_len = n - l;
  mpz_class C = 0;
  mpz_class D = 0;
  uint32_t m = 0;
  mpz_class modulus = 0;

  // [FFX2] pg 3., line 16
  for (uint32_t i = 0; i <= (r - 1); ++i) {
    if ((i & 1) == 0) {
      m = n / 2;
    } else {
      m = ceil(n / 2.0);
    }
    mpz_ui_pow_ui(modulus.get_mpz_t(), 2, m);

    RoundFunction(key, n, tweak, tweak_len_in_bits, i, B, m, &D);
    mpz_add(C.get_mpz_t(),
            A.get_mpz_t(),
            D.get_mpz_t());

    C = C % modulus;
    A = B;
    B = C;
  }

  // [FFX2] pg 3., line 19
  retval = (A << B_len) + B;

  mpz_ui_pow_ui(modulus.get_mpz_t(), 2, plaintext_len_in_bits);
  retval = retval % modulus;
}

bool Ffx::Decrypt(const std::string & key,
                  const mpz_class & tweak,
                  uint32_t tweak_len_in_bits,
                  const mpz_class & cihpertext,
                  uint32_t cihpertext_len_bits,
                  mpz_class * plaintext) {

  // [FFX2] pg 3., line 21
  if (!ValidateKey(key)) {
    return false;
  }

  mpz_class & retval = *plaintext;

  // [FFX2] pg 3., line 24-25
  uint32_t n = cihpertext_len_bits;
  uint32_t l = cihpertext_len_bits / 2;
  uint32_t r = kDefaultFfxRounds;
  mpz_class A, B;
  BitMask(cihpertext, cihpertext_len_bits, 0, l - 1, &A);
  BitMask(cihpertext, cihpertext_len_bits, l, n - 1, &B);
  uint32_t B_len = n - l;
  mpz_class C = 0;
  mpz_class D = 0;
  uint32_t m = 0;
  mpz_class modulus = 0;

  // [FFX2] pg 3., line 26
  for (int32_t i = r - 1; i >= 0; --i) {
    if ((i & 1) == 0) {
      m = n / 2;
    } else {
      m = ceil(n / 2.0);
    }
    mpz_ui_pow_ui(modulus.get_mpz_t(), 2, m);

    C = B;
    B = A;
    RoundFunction(key, n, tweak, tweak_len_in_bits, i, B, m, &D);
    mpz_sub(A.get_mpz_t(),
            C.get_mpz_t(),
            D.get_mpz_t());

    while(A < 0) A += modulus;
    A = A % modulus;
  }

  // [FFX2] pg 3., line 29
  retval = (A << B_len) + B;

  mpz_ui_pow_ui(modulus.get_mpz_t(), 2, cihpertext_len_bits);
  retval = retval % modulus;
}

/*
 * These are the main entry points with NULL tweaks.
 */
bool Ffx::Encrypt(const std::string & key,
                  const mpz_class & plaintext,
                  uint32_t plaintext_len_in_bits,
                  mpz_class * ciphertext) {
  return Ffx::Encrypt(key, 0, 0, plaintext, plaintext_len_in_bits, ciphertext);
}

bool Ffx::Decrypt(const std::string & key,
                  const mpz_class & ciphertext,
                  uint32_t ciphertext_len_in_bits,
                  mpz_class * plaintext) {
  return Ffx::Decrypt(key, 0, 0, ciphertext, ciphertext_len_in_bits, plaintext);
}

} // namespace ffx
<commit_msg>ensure we return in all functions<commit_after>/*
 * See ffx.h for details.
 *
 * References:
 *   [FFX2] http://csrc.nist.gov/groups/ST/toolkit/BCM/documents/proposedmodes/ffx/ffx-spec2.pdf
 */

#include "ffx/ffx.h"

#include <math.h>

#include <string>

#include "third_party/aes/aes.h"

namespace ffx {

static bool ValidateKey(const std::string & key) {
  if (key.length() != kFfxKeyLengthInNibbles) {
    return false;
  }
  for (uint32_t i = 0; i < key.length(); ++i) {
    if (isxdigit(key[i])) {
      continue;
    } else {
      return false;
    }
  }
  return true;
}

/*
 * This function has an unfortunate number of input parameters.
 * However, this is a direct implementation of the round-function algorithm
 * F_K(n,T,i,B) defined in [FFX2].
 */
static bool RoundFunction(const std::string & K,
                          uint32_t n,
                          const mpz_class & tweak,
                          uint32_t tweak_len_in_bits,
                          uint32_t i,
                          const mpz_class & B,
                          uint32_t B_len,
                          mpz_class * retval) {

  // [FFX2] pg 3., line 31
  uint32_t vers = 1;
  uint32_t t = ceil(tweak_len_in_bits / 8.0);
  uint32_t beta = ceil(n / 2.0);
  uint32_t b = ceil(beta / 8.0);
  uint32_t d = 4 * ceil(b / 4.0);

  // [FFX2] pg 3., line 32
  uint32_t m = 0;
  if ((i & 1) == 0) {
    m = n / 2;
  } else {
    m = ceil(n / 2.0);
  }

  // [FFX2] pg 3., line 33
  mpz_class P = 0;
  uint32_t P_len = (1 + 1 + 1 + 3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(vers)             << (1 + 1 + 3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(2)                << (1 + 3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(1)                << (3 + 1 + 1 + 4 + 4) * 8;
  P += mpz_class(2)                << (1 + 1 + 4 + 4) * 8;
  P += mpz_class(10)               << (1 + 4 + 4) * 8;
  P += mpz_class(n / 2)            << (4 + 4) * 8;
  P += mpz_class(n)                << (4) * 8;
  P += t;

  uint32_t B_bits = b * 8;

  // [FFX2] pg 3., line 34
  mpz_class Q = 0;
  uint32_t T_offset = ((((-1 * t) - b - 1) % 16) * 8);
  T_offset += 8;
  T_offset += B_bits;

  uint32_t Q_len = tweak_len_in_bits + T_offset;
  Q += mpz_class(tweak) << T_offset;
  Q += mpz_class(i) << B_bits;
  Q += B;

  mpz_class Y = 0;

  // [FFX2] pg 3., line 35
  bool cbc_success = AesCbcMac(K, (P << Q_len) + Q, P_len + Q_len, &Y);
  if (!cbc_success) {
    return false;
  }

  // [FFX2] pg 3., line 36
  mpz_class Z = Y;
  uint32_t Z_len = 16;
  mpz_class counter = 1;
  while (Z_len < (d + 4)) {
    mpz_class ctxt;
    AesEcbEncrypt(K, (Y + counter), 128, &ctxt);
    Z_len += 16;
    Z = Z << 128;
    Z += ctxt;
    ++counter;
  }

  // [FFX2] pg 3., line 37
  BitMask(Z, Z_len * 8, 0, ((d + 4) * 8) - 1, &Y);

  // [FFX2] pg 3., line 3=8
  mpz_class modulus = 0;
  mpz_ui_pow_ui(modulus.get_mpz_t(), 2, m);
  Y = Y % modulus;

  // [FFX2] pg 3., line 39
  *retval = Y;

  return true;
}

bool Ffx::Encrypt(const std::string & key ,
                  const mpz_class & tweak,
                  uint32_t tweak_len_in_bits,
                  const mpz_class & plaintext,
                  uint32_t plaintext_len_in_bits,
                  mpz_class * ciphertext) {

  // [FFX2] pg 3., line 11
  if (!ValidateKey(key)) {
    return false;
  }

  mpz_class & retval = *ciphertext;

  // [FFX2] pg 3., line 14-15
  uint32_t n = plaintext_len_in_bits;
  uint32_t l = plaintext_len_in_bits / 2;
  uint32_t r = kDefaultFfxRounds;
  mpz_class A, B;
  BitMask(plaintext, plaintext_len_in_bits, 0, l - 1, &A);
  BitMask(plaintext, plaintext_len_in_bits, l, n - 1, &B);
  uint32_t B_len = n - l;
  mpz_class C = 0;
  mpz_class D = 0;
  uint32_t m = 0;
  mpz_class modulus = 0;

  // [FFX2] pg 3., line 16
  for (uint32_t i = 0; i <= (r - 1); ++i) {
    if ((i & 1) == 0) {
      m = n / 2;
    } else {
      m = ceil(n / 2.0);
    }
    mpz_ui_pow_ui(modulus.get_mpz_t(), 2, m);

    RoundFunction(key, n, tweak, tweak_len_in_bits, i, B, m, &D);
    mpz_add(C.get_mpz_t(),
            A.get_mpz_t(),
            D.get_mpz_t());

    C = C % modulus;
    A = B;
    B = C;
  }

  // [FFX2] pg 3., line 19
  retval = (A << B_len) + B;

  mpz_ui_pow_ui(modulus.get_mpz_t(), 2, plaintext_len_in_bits);
  retval = retval % modulus;

  return true;
}

bool Ffx::Decrypt(const std::string & key,
                  const mpz_class & tweak,
                  uint32_t tweak_len_in_bits,
                  const mpz_class & cihpertext,
                  uint32_t cihpertext_len_bits,
                  mpz_class * plaintext) {

  // [FFX2] pg 3., line 21
  if (!ValidateKey(key)) {
    return false;
  }

  mpz_class & retval = *plaintext;

  // [FFX2] pg 3., line 24-25
  uint32_t n = cihpertext_len_bits;
  uint32_t l = cihpertext_len_bits / 2;
  uint32_t r = kDefaultFfxRounds;
  mpz_class A, B;
  BitMask(cihpertext, cihpertext_len_bits, 0, l - 1, &A);
  BitMask(cihpertext, cihpertext_len_bits, l, n - 1, &B);
  uint32_t B_len = n - l;
  mpz_class C = 0;
  mpz_class D = 0;
  uint32_t m = 0;
  mpz_class modulus = 0;

  // [FFX2] pg 3., line 26
  for (int32_t i = r - 1; i >= 0; --i) {
    if ((i & 1) == 0) {
      m = n / 2;
    } else {
      m = ceil(n / 2.0);
    }
    mpz_ui_pow_ui(modulus.get_mpz_t(), 2, m);

    C = B;
    B = A;
    RoundFunction(key, n, tweak, tweak_len_in_bits, i, B, m, &D);
    mpz_sub(A.get_mpz_t(),
            C.get_mpz_t(),
            D.get_mpz_t());

    while(A < 0) A += modulus;
    A = A % modulus;
  }

  // [FFX2] pg 3., line 29
  retval = (A << B_len) + B;

  mpz_ui_pow_ui(modulus.get_mpz_t(), 2, cihpertext_len_bits);
  retval = retval % modulus;

  return true;
}

/*
 * These are the main entry points with NULL tweaks.
 */
bool Ffx::Encrypt(const std::string & key,
                  const mpz_class & plaintext,
                  uint32_t plaintext_len_in_bits,
                  mpz_class * ciphertext) {
  return Ffx::Encrypt(key, 0, 0, plaintext, plaintext_len_in_bits, ciphertext);
}

bool Ffx::Decrypt(const std::string & key,
                  const mpz_class & ciphertext,
                  uint32_t ciphertext_len_in_bits,
                  mpz_class * plaintext) {
  return Ffx::Decrypt(key, 0, 0, ciphertext, ciphertext_len_in_bits, plaintext);
}

} // namespace ffx
<|endoftext|>


<commit_before>#include <vector>
#include <cstdlib>
#include <iostream>
#include <unordered_map>
#include <sstream>
#include <string>
#include "vg.hpp"
#include "xg.hpp"
#include "vg.pb.h"

/**
 * Provides a way to filter Edits contained
 * within Alignments. This can be used to clean out
 * sequencing errors and to find high-quality candidates
 * for variant calling.
 *
 */
namespace vg{
class Filter{
    public:
        Filter();
        ~Filter();
         /* Filter functions.
         * Take an Alignment and walk it.
         * Perform the desired summary statistics.
         * Output the Alignment if it passes OR
         * A new, modified Alignment if we allow it OR
         * an empty Alignment if the alignment fails and we don't allow
         * modified alignments.
         */
        Alignment depth_filter(Alignment& aln);
        Alignment qual_filter(Alignment& aln);
        Alignment coverage_filter(Alignment& aln);
        Alignment avg_qual_filter(Alignment& aln);
        Alignment percent_identity_filter(Alignment& aln);
        Alignment soft_clip_filter(Alignment& aln);
        Alignment split_read_filter(Alignment& aln);
        Alignment path_divergence_filter(Alignment& aln);
        Alignment reversing_filter(Alignment& aln);
        Alignment kmer_filter(Alignment& aln);
        void set_min_depth(int depth);
        //void set_min_kmer_depth(int d);
        void set_min_qual(int qual);
        void set_min_percent_identity(double pct_id);
        void set_avg_qual(double avg_qual);
        void set_filter_matches(bool fm);
        void set_remove_failing_edits(bool fm);
        void set_soft_clip_limit(int max_clip);
        void set_split_read_limit(int split_limit);
        void set_reversing(bool do_reversing_filter);
        void set_path_divergence(bool do_path_divergence);
        void set_window_length(int window_length);
        void set_my_vg(vg::VG* vg);
        void set_my_xg_idx(xg::XG* xg_idx);
        void set_inverse(bool do_inv);

        int get_min_depth();
        int get_min_qual();
        int get_window_length();
        int get_soft_clip_limit();
        int get_split_read_limit();
        double get_min_percent_identity();
        double get_min_avg_qual();
        bool get_inverse();
        bool get_filter_matches();
        bool get_remove_failing_edits();
        bool get_do_path_divergence();
        bool get_do_reversing();

    private:
        vg::VG* my_vg;
        xg::XG* my_xg_idx;
        //Position: NodeID + offset
        // different edits may be present at each position.
        // is there some way to just hash the mappings?
        unordered_map<string, unordered_map<string, int> > pos_to_edit_to_depth;
        unordered_map<int, int> pos_to_qual;
        bool inverse = false;
        bool remove_failing_edits = false;
        bool filter_matches = false;
        bool do_path_divergence;
        bool do_reversing;
        int min_depth = 0;
        int min_qual = 0;
        int min_cov = 0;
        int window_length = 0;
        int qual_offset = 0;
        int soft_clip_limit = -1;
        int split_read_limit = -1;
        double min_percent_identity = 0.0;
        double min_avg_qual = 0.0;
        };
}
<commit_msg>Add include guards to filter.hpp<commit_after>#ifndef VG_FILTER_HPP
#define VG_FILTER_HPP

#include <vector>
#include <cstdlib>
#include <iostream>
#include <unordered_map>
#include <sstream>
#include <string>
#include "vg.hpp"
#include "xg.hpp"
#include "vg.pb.h"

/**
 * Provides a way to filter Edits contained
 * within Alignments. This can be used to clean out
 * sequencing errors and to find high-quality candidates
 * for variant calling.
 *
 */
namespace vg{
class Filter{
    public:
        Filter();
        ~Filter();
         /* Filter functions.
         * Take an Alignment and walk it.
         * Perform the desired summary statistics.
         * Output the Alignment if it passes OR
         * A new, modified Alignment if we allow it OR
         * an empty Alignment if the alignment fails and we don't allow
         * modified alignments.
         */
        Alignment depth_filter(Alignment& aln);
        Alignment qual_filter(Alignment& aln);
        Alignment coverage_filter(Alignment& aln);
        Alignment avg_qual_filter(Alignment& aln);
        Alignment percent_identity_filter(Alignment& aln);
        Alignment soft_clip_filter(Alignment& aln);
        Alignment split_read_filter(Alignment& aln);
        Alignment path_divergence_filter(Alignment& aln);
        Alignment reversing_filter(Alignment& aln);
        Alignment kmer_filter(Alignment& aln);
        void set_min_depth(int depth);
        //void set_min_kmer_depth(int d);
        void set_min_qual(int qual);
        void set_min_percent_identity(double pct_id);
        void set_avg_qual(double avg_qual);
        void set_filter_matches(bool fm);
        void set_remove_failing_edits(bool fm);
        void set_soft_clip_limit(int max_clip);
        void set_split_read_limit(int split_limit);
        void set_reversing(bool do_reversing_filter);
        void set_path_divergence(bool do_path_divergence);
        void set_window_length(int window_length);
        void set_my_vg(vg::VG* vg);
        void set_my_xg_idx(xg::XG* xg_idx);
        void set_inverse(bool do_inv);

        int get_min_depth();
        int get_min_qual();
        int get_window_length();
        int get_soft_clip_limit();
        int get_split_read_limit();
        double get_min_percent_identity();
        double get_min_avg_qual();
        bool get_inverse();
        bool get_filter_matches();
        bool get_remove_failing_edits();
        bool get_do_path_divergence();
        bool get_do_reversing();

    private:
        vg::VG* my_vg;
        xg::XG* my_xg_idx;
        //Position: NodeID + offset
        // different edits may be present at each position.
        // is there some way to just hash the mappings?
        unordered_map<string, unordered_map<string, int> > pos_to_edit_to_depth;
        unordered_map<int, int> pos_to_qual;
        bool inverse = false;
        bool remove_failing_edits = false;
        bool filter_matches = false;
        bool do_path_divergence;
        bool do_reversing;
        int min_depth = 0;
        int min_qual = 0;
        int min_cov = 0;
        int window_length = 0;
        int qual_offset = 0;
        int soft_clip_limit = -1;
        int split_read_limit = -1;
        double min_percent_identity = 0.0;
        double min_avg_qual = 0.0;
        };
}
#endif
<|endoftext|>


<commit_before>/*
* mod_dup - duplicates apache requests
*
* Copyright (C) 2013 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "mod_dup.hh"

#include <http_config.h>

namespace DupModule {

const unsigned int CMaxBytes = 8192;

bool
extractBrigadeContent(apr_bucket_brigade *bb, request_rec *pRequest, std::string &content) {
    if (ap_get_brigade(pRequest->input_filters,
                       bb, AP_MODE_READBYTES, APR_BLOCK_READ, CMaxBytes) == APR_SUCCESS) {
        // Read brigade content
        for (apr_bucket *b = APR_BRIGADE_FIRST(bb);
             b != APR_BRIGADE_SENTINEL(bb);
             b = APR_BUCKET_NEXT(b) ) {
            // Metadata end of stream
            if (APR_BUCKET_IS_EOS(b)) {
                return true;
            }
            const char *data = 0;
            apr_size_t len = 0;
            apr_status_t rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
            if (rv != APR_SUCCESS) {
                Log::error(42, "Bucket read failed, skipping the rest of the body");
                return true;
            }
            if (len) {
                content.append(data, len);
            }
        }
    }
    else {
        Log::error(42, "Get brigade failed, skipping the rest of the body");
        return true;
    }
    return false;
}

int
translateHook(request_rec *pRequest) {
    if (!pRequest->per_dir_config)
        return DECLINED;
    DupConf *tConf = reinterpret_cast<DupConf *>(ap_get_module_config(pRequest->per_dir_config, &dup_module));
    if (!tConf || !tConf->dirName) {
        // Not a location that we treat, we decline the request
        return DECLINED;
    }
    RequestInfo *info = new RequestInfo(tConf->getNextReqId());
    // Backup in request context
    ap_set_module_config(pRequest->request_config, &dup_module, (void *)info);

    if (!pRequest->connection->pool) {
        Log::error(42, "No connection pool associated to the request");
        return DECLINED;
    }
    if (!pRequest->connection->bucket_alloc) {
        pRequest->connection->bucket_alloc = apr_bucket_alloc_create(pRequest->connection->pool);
        if (!pRequest->connection->bucket_alloc) {
            Log::error(42, "Request bucket allocation failed");
            return DECLINED;
        }
    }

    apr_bucket_brigade *bb = apr_brigade_create(pRequest->connection->pool, pRequest->connection->bucket_alloc);
    if (!bb) {
        Log::error(42, "Bucket brigade allocation failed");
        return DECLINED;
    }
    while (!extractBrigadeContent(bb, pRequest, info->mBody)){
        apr_brigade_cleanup(bb);
    }
    apr_brigade_cleanup(bb);
    // Body read :)

    // Copy Request ID in both headers
    std::string reqId = boost::lexical_cast<std::string>(info->mId);
    apr_table_set(pRequest->headers_in, c_UNIQUE_ID, reqId.c_str());
    apr_table_set(pRequest->headers_out, c_UNIQUE_ID, reqId.c_str());

    // Synchronous context enrichment
    info->mConfPath = tConf->dirName;
    info->mArgs = pRequest->args ? pRequest->args : "";
    gProcessor->enrichContext(pRequest, *info);

    return DECLINED;
}

/**
 * Reinject the body we saved in earlyHook into a brigade
 */
apr_status_t
inputFilterBody2Brigade(ap_filter_t *pF, apr_bucket_brigade *pB, ap_input_mode_t pMode, apr_read_type_e pBlock, apr_off_t pReadbytes)
{

    request_rec *pRequest = pF->r;
    if (!pRequest || !pRequest->per_dir_config) {
        apr_bucket *e = apr_bucket_eos_create(pF->c->bucket_alloc);
        assert(e);
        APR_BRIGADE_INSERT_TAIL(pB, e);
        return APR_SUCCESS;
    }
    // Retrieve request info from context
    RequestInfo *info = reinterpret_cast<RequestInfo *>(ap_get_module_config(pRequest->request_config, &dup_module));
    if (!info) {
        // Should not happen
        apr_bucket *e = apr_bucket_eos_create(pF->c->bucket_alloc);
        assert(e);
        APR_BRIGADE_INSERT_TAIL(pB, e);
        return APR_SUCCESS;
    }
    if (!pF->ctx) {
        if (!info->mBody.empty()) {
            apr_status_t st;
            Log::warn(1, "Wrote body to brigade: %s",  info->mBody.c_str());
            if ((st = apr_brigade_write(pB, NULL, NULL, info->mBody.c_str(), info->mBody.size())) != APR_SUCCESS ) {
                Log::warn(1, "Failed to write request body in a brigade: %s",  info->mBody.c_str());
                return st;
            }
        }
        // Marks the body as sent
        pF->ctx = (void *) -1;
    }
    // Marks that we do not have any more data to read
    apr_bucket *e = apr_bucket_eos_create(pF->c->bucket_alloc);
    assert(e);
    APR_BRIGADE_INSERT_TAIL(pB, e);
    return APR_SUCCESS;
}

/*
 * Callback to iterate over the headers tables
 * Pushes a copy of key => value in a list
 */
static int iterateOverHeadersCallBack(void *d, const char *key, const char *value) {
    RequestInfo::tHeaders *headers = reinterpret_cast<RequestInfo::tHeaders *>(d);
    headers->push_back(std::pair<std::string, std::string>(key, value));
    return 1;
}

static void
prepareRequestInfo(DupConf *tConf, request_rec *pRequest, RequestInfo &r, bool withAnswer) {
    // Basic
    r.mPoison = false;
    r.mConfPath = tConf->dirName;
    r.mPath = pRequest->uri;
    r.mArgs = pRequest->args ? pRequest->args : "";

    // Copy headers in
    apr_table_do(&iterateOverHeadersCallBack, &r.mHeadersIn, pRequest->headers_in, NULL);
    if (withAnswer) {
        // Copy headers out
        apr_table_do(&iterateOverHeadersCallBack, &r.mHeadersOut, pRequest->headers_out, NULL);
    }
}

static void
printRequest(request_rec *pRequest, RequestInfo *pBH, DupConf *tConf) {
    const char *reqId = apr_table_get(pRequest->headers_in, c_UNIQUE_ID);
    Log::debug("### Pushing a request with ID: %s, body size:%ld", reqId, pBH->mBody.size());
    Log::debug("### Uri:%s, dir name:%s", pRequest->uri, tConf->dirName);
    Log::debug("### Request args: %s", pRequest->args);
}

/*
 * Request context used during brigade run
 */
class RequestContext {
public:
    apr_bucket_brigade  *mTmpBB;
    RequestInfo         *mReq; /** Req is pushed to requestprocessor for duplication and will be deleted later*/

    RequestContext(ap_filter_t *pFilter) {
        mTmpBB = apr_brigade_create(pFilter->r->pool, pFilter->c->bucket_alloc);
        mReq = reinterpret_cast<RequestInfo *>(ap_get_module_config(pFilter->r->request_config,
                                                                    &dup_module));
        assert(mReq);
    }

    RequestContext()
        : mTmpBB(NULL)
        , mReq(NULL) {
    }

    ~RequestContext() {
        if (mTmpBB) {
            apr_brigade_cleanup(mTmpBB);
        }
    }
};

/**
 * Here we can get the response body and finally duplicate or not
 */
apr_status_t
outputFilterHandler(ap_filter_t *pFilter, apr_bucket_brigade *pBrigade) {
    request_rec *pRequest = pFilter->r;
    // Reject requests that do not meet our requirements
    if (!pRequest || !pRequest->per_dir_config)
        return ap_pass_brigade(pFilter->next, pBrigade);

    struct DupConf *tConf = reinterpret_cast<DupConf *>(ap_get_module_config(pRequest->per_dir_config, &dup_module));
    if ((!tConf) || (!tConf->dirName) || (tConf->getHighestDuplicationType() == DuplicationType::NONE)) {
         return ap_pass_brigade(pFilter->next, pBrigade);
    }

    // Request answer analyse
    RequestContext *ctx = static_cast<RequestContext *>(pFilter->ctx);
    if (ctx == NULL) {
        // First pass in the output filter => context init
        ctx = new RequestContext(pFilter);
        pFilter->ctx = ctx;
    } else if (ctx == (void *) -1) {
        // Output filter already did his job, we return
        return ap_pass_brigade(pFilter->next, pBrigade);
    }

    // We need to get the highest one as we haven't matched which rule it is yet
    if (tConf->getHighestDuplicationType() != DuplicationType::REQUEST_WITH_ANSWER) {
        // Asynchronous push of request WITHOUT the answer
        RequestInfo *rH = ctx->mReq;
        prepareRequestInfo(tConf, pRequest, *rH, false);
        printRequest(pRequest, rH, tConf);
        gThreadPool->push(rH);
        delete ctx;
        pFilter->ctx = (void *) -1;
        return ap_pass_brigade(pFilter->next, pBrigade);
    }
    // Asynchronous push of request WITH the answer
    apr_bucket *currentBucket;
    while ((currentBucket = APR_BRIGADE_FIRST(pBrigade)) != APR_BRIGADE_SENTINEL(pBrigade)) {
        const char *data;
        apr_size_t len;
        apr_status_t rv;
        rv = apr_bucket_read(currentBucket, &data, &len, APR_BLOCK_READ);

        if ((rv == APR_SUCCESS) && (data != NULL)) {
            ctx->mReq->mAnswer.append(data, len);
        }
        /* Remove bucket e from bb. */
        APR_BUCKET_REMOVE(currentBucket);
        /* Insert it into  temporary brigade. */
        APR_BRIGADE_INSERT_HEAD(ctx->mTmpBB, currentBucket);
        /* Pass brigade downstream. */
        rv = ap_pass_brigade(pFilter->next, ctx->mTmpBB);
        if (rv != APR_SUCCESS) {
            // Something went wrong, no duplication performed
            delete ctx;
            pFilter->ctx = (void *) -1;
            return rv;
        }
        if (APR_BUCKET_IS_EOS(currentBucket)) {
            // Pushing the answer to the processor
            prepareRequestInfo(tConf, pRequest, *(ctx->mReq), true);
            printRequest(pRequest, ctx->mReq, tConf);
            gThreadPool->push(ctx->mReq);
            delete ctx;
            pFilter->ctx = (void *) -1;
        }
        else {
            apr_brigade_cleanup(ctx->mTmpBB);
        }
    }
    return OK;
}

};
<commit_msg>Modified request context with shared pointer<commit_after>/*
* mod_dup - duplicates apache requests
*
* Copyright (C) 2013 Orange
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "mod_dup.hh"

#include <boost/shared_ptr.hpp>
#include <http_config.h>

namespace DupModule {

const unsigned int CMaxBytes = 8192;

bool
extractBrigadeContent(apr_bucket_brigade *bb, request_rec *pRequest, std::string &content) {
    if (ap_get_brigade(pRequest->input_filters,
                       bb, AP_MODE_READBYTES, APR_BLOCK_READ, CMaxBytes) == APR_SUCCESS) {
        // Read brigade content
        for (apr_bucket *b = APR_BRIGADE_FIRST(bb);
             b != APR_BRIGADE_SENTINEL(bb);
             b = APR_BUCKET_NEXT(b) ) {
            // Metadata end of stream
            if (APR_BUCKET_IS_EOS(b)) {
                return true;
            }
            const char *data = 0;
            apr_size_t len = 0;
            apr_status_t rv = apr_bucket_read(b, &data, &len, APR_BLOCK_READ);
            if (rv != APR_SUCCESS) {
                Log::error(42, "Bucket read failed, skipping the rest of the body");
                return true;
            }
            if (len) {
                content.append(data, len);
            }
        }
    }
    else {
        Log::error(42, "Get brigade failed, skipping the rest of the body");
        return true;
    }
    return false;
}

int
translateHook(request_rec *pRequest) {
    if (!pRequest->per_dir_config)
        return DECLINED;
    DupConf *tConf = reinterpret_cast<DupConf *>(ap_get_module_config(pRequest->per_dir_config, &dup_module));
    if (!tConf || !tConf->dirName) {
        // Not a location that we treat, we decline the request
        return DECLINED;
    }
    RequestInfo *info = new RequestInfo(tConf->getNextReqId());
    // Allocation on a shared pointer on the request pool
    // We guarantee that whatever happens, the RequestInfo will be deleted
    void *space = apr_palloc(pRequest->pool, sizeof(boost::shared_ptr<RequestInfo>));
    new (space) boost::shared_ptr<RequestInfo>(info);
    // Registering of the shared pointer destructor on the pool
    apr_pool_cleanup_register(pRequest->pool, space, cleaner<boost::shared_ptr<RequestInfo> >,
                              apr_pool_cleanup_null);
    // Backup in request context
    ap_set_module_config(pRequest->request_config, &dup_module, (void *)info);

    if (!pRequest->connection->pool) {
        Log::error(42, "No connection pool associated to the request");
        return DECLINED;
    }
    if (!pRequest->connection->bucket_alloc) {
        pRequest->connection->bucket_alloc = apr_bucket_alloc_create(pRequest->connection->pool);
        if (!pRequest->connection->bucket_alloc) {
            Log::error(42, "Request bucket allocation failed");
            return DECLINED;
        }
    }

    apr_bucket_brigade *bb = apr_brigade_create(pRequest->connection->pool, pRequest->connection->bucket_alloc);
    if (!bb) {
        Log::error(42, "Bucket brigade allocation failed");
        return DECLINED;
    }
    while (!extractBrigadeContent(bb, pRequest, info->mBody)){
        apr_brigade_cleanup(bb);
    }
    apr_brigade_cleanup(bb);
    // Body read :)

    // Copy Request ID in both headers
    std::string reqId = boost::lexical_cast<std::string>(info->mId);
    apr_table_set(pRequest->headers_in, c_UNIQUE_ID, reqId.c_str());
    apr_table_set(pRequest->headers_out, c_UNIQUE_ID, reqId.c_str());

    // Synchronous context enrichment
    info->mConfPath = tConf->dirName;
    info->mArgs = pRequest->args ? pRequest->args : "";
    gProcessor->enrichContext(pRequest, *info);

    return DECLINED;
}

/**
 * Reinject the body we saved in earlyHook into a brigade
 */
apr_status_t
inputFilterBody2Brigade(ap_filter_t *pF, apr_bucket_brigade *pB, ap_input_mode_t pMode, apr_read_type_e pBlock, apr_off_t pReadbytes)
{

    request_rec *pRequest = pF->r;
    if (!pRequest || !pRequest->per_dir_config) {
        apr_bucket *e = apr_bucket_eos_create(pF->c->bucket_alloc);
        assert(e);
        APR_BRIGADE_INSERT_TAIL(pB, e);
        return APR_SUCCESS;
    }
    // Retrieve request info from context
    boost::shared_ptr<RequestInfo> *shPtr = reinterpret_cast<boost::shared_ptr<RequestInfo> *>(ap_get_module_config(pRequest->request_config, &dup_module));
    RequestInfo *info = shPtr->get();

    if (!info) {
        // Should not happen
        apr_bucket *e = apr_bucket_eos_create(pF->c->bucket_alloc);
        assert(e);
        APR_BRIGADE_INSERT_TAIL(pB, e);
        return APR_SUCCESS;
    }
    if (!pF->ctx) {
        if (!info->mBody.empty()) {
            apr_status_t st;
            Log::warn(1, "Wrote body to brigade: %s",  info->mBody.c_str());
            if ((st = apr_brigade_write(pB, NULL, NULL, info->mBody.c_str(), info->mBody.size())) != APR_SUCCESS ) {
                Log::warn(1, "Failed to write request body in a brigade: %s",  info->mBody.c_str());
                return st;
            }
        }
        // Marks the body as sent
        pF->ctx = (void *) -1;
    }
    // Marks that we do not have any more data to read
    apr_bucket *e = apr_bucket_eos_create(pF->c->bucket_alloc);
    assert(e);
    APR_BRIGADE_INSERT_TAIL(pB, e);
    return APR_SUCCESS;
}

/*
 * Callback to iterate over the headers tables
 * Pushes a copy of key => value in a list
 */
static int iterateOverHeadersCallBack(void *d, const char *key, const char *value) {
    RequestInfo::tHeaders *headers = reinterpret_cast<RequestInfo::tHeaders *>(d);
    headers->push_back(std::pair<std::string, std::string>(key, value));
    return 1;
}

static void
prepareRequestInfo(DupConf *tConf, request_rec *pRequest, RequestInfo &r, bool withAnswer) {
    // Basic
    r.mPoison = false;
    r.mConfPath = tConf->dirName;
    r.mPath = pRequest->uri;
    r.mArgs = pRequest->args ? pRequest->args : "";

    // Copy headers in
    apr_table_do(&iterateOverHeadersCallBack, &r.mHeadersIn, pRequest->headers_in, NULL);
    if (withAnswer) {
        // Copy headers out
        apr_table_do(&iterateOverHeadersCallBack, &r.mHeadersOut, pRequest->headers_out, NULL);
    }
}

static void
printRequest(request_rec *pRequest, RequestInfo *pBH, DupConf *tConf) {
    const char *reqId = apr_table_get(pRequest->headers_in, c_UNIQUE_ID);
    Log::debug("### Pushing a request with ID: %s, body size:%ld", reqId, pBH->mBody.size());
    Log::debug("### Uri:%s, dir name:%s", pRequest->uri, tConf->dirName);
    Log::debug("### Request args: %s", pRequest->args);
}

/*
 * Request context used during brigade run
 */
class RequestContext {
public:
    apr_bucket_brigade  *mTmpBB;
    RequestInfo         *mReq; /** Req is pushed to requestprocessor for duplication and will be deleted later*/

    RequestContext(ap_filter_t *pFilter) {
        mTmpBB = apr_brigade_create(pFilter->r->pool, pFilter->c->bucket_alloc);
        boost::shared_ptr<RequestInfo> *shPtr = reinterpret_cast<boost::shared_ptr<RequestInfo> *>(ap_get_module_config(pFilter->r->request_config, &dup_module));
        mReq = shPtr->get();
        assert(mReq);
    }

    RequestContext()
        : mTmpBB(NULL)
        , mReq(NULL) {
    }

    ~RequestContext() {
        if (mTmpBB) {
            apr_brigade_cleanup(mTmpBB);
        }
    }
};

/**
 * Here we can get the response body and finally duplicate or not
 */
apr_status_t
outputFilterHandler(ap_filter_t *pFilter, apr_bucket_brigade *pBrigade) {
    request_rec *pRequest = pFilter->r;
    // Reject requests that do not meet our requirements
    if (!pRequest || !pRequest->per_dir_config)
        return ap_pass_brigade(pFilter->next, pBrigade);

    struct DupConf *tConf = reinterpret_cast<DupConf *>(ap_get_module_config(pRequest->per_dir_config, &dup_module));
    if ((!tConf) || (!tConf->dirName) || (tConf->getHighestDuplicationType() == DuplicationType::NONE)) {
         return ap_pass_brigade(pFilter->next, pBrigade);
    }

    // Request answer analyse
    RequestContext *ctx = static_cast<RequestContext *>(pFilter->ctx);
    if (ctx == NULL) {
        // First pass in the output filter => context init
        ctx = new RequestContext(pFilter);
        pFilter->ctx = ctx;
    } else if (ctx == (void *) -1) {
        // Output filter already did his job, we return
        return ap_pass_brigade(pFilter->next, pBrigade);
    }

    // We need to get the highest one as we haven't matched which rule it is yet
    if (tConf->getHighestDuplicationType() != DuplicationType::REQUEST_WITH_ANSWER) {
        // Asynchronous push of request WITHOUT the answer
        RequestInfo *rH = ctx->mReq;
        prepareRequestInfo(tConf, pRequest, *rH, false);
        printRequest(pRequest, rH, tConf);
        gThreadPool->push(rH);
        delete ctx;
        pFilter->ctx = (void *) -1;
        return ap_pass_brigade(pFilter->next, pBrigade);
    }
    // Asynchronous push of request WITH the answer
    apr_bucket *currentBucket;
    while ((currentBucket = APR_BRIGADE_FIRST(pBrigade)) != APR_BRIGADE_SENTINEL(pBrigade)) {
        const char *data;
        apr_size_t len;
        apr_status_t rv;
        rv = apr_bucket_read(currentBucket, &data, &len, APR_BLOCK_READ);

        if ((rv == APR_SUCCESS) && (data != NULL)) {
            ctx->mReq->mAnswer.append(data, len);
        }
        /* Remove bucket e from bb. */
        APR_BUCKET_REMOVE(currentBucket);
        /* Insert it into  temporary brigade. */
        APR_BRIGADE_INSERT_HEAD(ctx->mTmpBB, currentBucket);
        /* Pass brigade downstream. */
        rv = ap_pass_brigade(pFilter->next, ctx->mTmpBB);
        if (rv != APR_SUCCESS) {
            // Something went wrong, no duplication performed
            delete ctx;
            pFilter->ctx = (void *) -1;
            return rv;
        }
        if (APR_BUCKET_IS_EOS(currentBucket)) {
            // Pushing the answer to the processor
            prepareRequestInfo(tConf, pRequest, *(ctx->mReq), true);
            printRequest(pRequest, ctx->mReq, tConf);
            gThreadPool->push(ctx->mReq);
            delete ctx;
            pFilter->ctx = (void *) -1;
        }
        else {
            apr_brigade_cleanup(ctx->mTmpBB);
        }
    }
    return OK;
}

};
<|endoftext|>


<commit_before>/*
 * Copyright 2015 Cloudius Systems
 */

#pragma once

#include "types.hh"
#include "atomic_cell.hh"
#include "query-request.hh"
#include "query-result.hh"

// Refer to query-result.hh for the query result format

namespace query {

class result::row_writer {
    bytes_ostream& _w;
    const partition_slice& _slice;
    bytes_ostream::place_holder<uint32_t> _size_ph;
    size_t _start_pos;
    bool _finished = false;
public:
    row_writer(
        const partition_slice& slice,
        bytes_ostream& w,
        bytes_ostream::place_holder<uint32_t> size_ph)
        : _w(w)
        , _slice(slice)
        , _size_ph(size_ph)
        , _start_pos(w.size())
    { }

    ~row_writer() {
        assert(_finished);
    }

    void add_empty() {
        // FIXME: store this in a bitmap
        _w.write<int8_t>(false);
    }

    void add(::atomic_cell_view c) {
        // FIXME: store this in a bitmap
        _w.write<int8_t>(true);
        assert(c.is_live());
        if (_slice.options.contains<partition_slice::option::send_timestamp_and_expiry>()) {
            _w.write(c.timestamp());
            if (c.is_live_and_has_ttl()) {
                _w.write<gc_clock::rep>(c.expiry().time_since_epoch().count());
            } else {
                _w.write<gc_clock::rep>(std::numeric_limits<gc_clock::rep>::max());
            }
        }
        _w.write_blob(c.value());
    }

    void add(collection_mutation::view v) {
        // FIXME: store this in a bitmap
        _w.write<int8_t>(true);
        _w.write_blob(v.data);
    }

    void finish() {
        auto row_size = _w.size() - _start_pos;
        assert((uint32_t)row_size == row_size);
        _w.set(_size_ph, (uint32_t)row_size);
        _finished = true;
    }
};

// Call finish() or retract() when done.
class result::partition_writer {
    bytes_ostream& _w;
    const partition_slice& _slice;
    bytes_ostream::place_holder<uint32_t> _count_ph;
    bytes_ostream::position _pos;
    uint32_t _row_count = 0;
    bool _static_row_added = false;
    bool _finished = false;
public:
    partition_writer(
        const partition_slice& slice,
        bytes_ostream::place_holder<uint32_t> count_ph,
        bytes_ostream::position pos,
        bytes_ostream& w)
        : _w(w)
        , _slice(slice)
        , _count_ph(count_ph)
        , _pos(pos)
    { }

    ~partition_writer() {
        assert(_finished);
    }

    row_writer add_row(const clustering_key& key) {
        if (_slice.options.contains<partition_slice::option::send_clustering_key>()) {
            _w.write_blob(key);
        }
        ++_row_count;
        auto size_placeholder = _w.write_place_holder<uint32_t>();
        return row_writer(_slice, _w, size_placeholder);
    }

    // Call before any add_row()
    row_writer add_static_row() {
        assert(!_static_row_added); // Static row can be added only once
        assert(!_row_count); // Static row must be added before clustered rows
        _static_row_added = true;
        auto size_placeholder = _w.write_place_holder<uint32_t>();
        return row_writer(_slice, _w, size_placeholder);
    }

    uint32_t row_count() const {
        return _row_count;
    }

    void finish() {
        _w.set(_count_ph, _row_count);

        // The partition is live. If there are no clustered rows, there
        // must be something live in the static row, which counts as one row.
        _row_count = std::max<uint32_t>(_row_count, 1);

        _finished = true;
    }

    void retract() {
        _row_count = 0;
        _w.retract(_pos);
        _finished = true;
    }

    const partition_slice& slice() const {
        return _slice;
    }
};

class result::builder {
    bytes_ostream _w;
    const partition_slice& _slice;
public:
    builder(const partition_slice& slice) : _slice(slice) { }

    // Starts new partition and returns a builder for its contents.
    // Invalidates all previously obtained builders
    partition_writer add_partition(const partition_key& key) {
        auto pos = _w.pos();
        auto count_place_holder = _w.write_place_holder<uint32_t>();
        if (_slice.options.contains<partition_slice::option::send_partition_key>()) {
            _w.write_blob(key);
        }
        return partition_writer(_slice, count_place_holder, pos, _w);
    }

    result build() {
        return result(std::move(_w));
    };

    const partition_slice& slice() const {
        return _slice;
    }
};

}
<commit_msg>query-result-writer: Remove assert(_finished) guards from destructors<commit_after>/*
 * Copyright 2015 Cloudius Systems
 */

#pragma once

#include "types.hh"
#include "atomic_cell.hh"
#include "query-request.hh"
#include "query-result.hh"

// Refer to query-result.hh for the query result format

namespace query {

class result::row_writer {
    bytes_ostream& _w;
    const partition_slice& _slice;
    bytes_ostream::place_holder<uint32_t> _size_ph;
    size_t _start_pos;
public:
    row_writer(
        const partition_slice& slice,
        bytes_ostream& w,
        bytes_ostream::place_holder<uint32_t> size_ph)
        : _w(w)
        , _slice(slice)
        , _size_ph(size_ph)
        , _start_pos(w.size())
    { }

    void add_empty() {
        // FIXME: store this in a bitmap
        _w.write<int8_t>(false);
    }

    void add(::atomic_cell_view c) {
        // FIXME: store this in a bitmap
        _w.write<int8_t>(true);
        assert(c.is_live());
        if (_slice.options.contains<partition_slice::option::send_timestamp_and_expiry>()) {
            _w.write(c.timestamp());
            if (c.is_live_and_has_ttl()) {
                _w.write<gc_clock::rep>(c.expiry().time_since_epoch().count());
            } else {
                _w.write<gc_clock::rep>(std::numeric_limits<gc_clock::rep>::max());
            }
        }
        _w.write_blob(c.value());
    }

    void add(collection_mutation::view v) {
        // FIXME: store this in a bitmap
        _w.write<int8_t>(true);
        _w.write_blob(v.data);
    }

    void finish() {
        auto row_size = _w.size() - _start_pos;
        assert((uint32_t)row_size == row_size);
        _w.set(_size_ph, (uint32_t)row_size);
    }
};

// Call finish() or retract() when done.
class result::partition_writer {
    bytes_ostream& _w;
    const partition_slice& _slice;
    bytes_ostream::place_holder<uint32_t> _count_ph;
    bytes_ostream::position _pos;
    uint32_t _row_count = 0;
    bool _static_row_added = false;
public:
    partition_writer(
        const partition_slice& slice,
        bytes_ostream::place_holder<uint32_t> count_ph,
        bytes_ostream::position pos,
        bytes_ostream& w)
        : _w(w)
        , _slice(slice)
        , _count_ph(count_ph)
        , _pos(pos)
    { }

    row_writer add_row(const clustering_key& key) {
        if (_slice.options.contains<partition_slice::option::send_clustering_key>()) {
            _w.write_blob(key);
        }
        ++_row_count;
        auto size_placeholder = _w.write_place_holder<uint32_t>();
        return row_writer(_slice, _w, size_placeholder);
    }

    // Call before any add_row()
    row_writer add_static_row() {
        assert(!_static_row_added); // Static row can be added only once
        assert(!_row_count); // Static row must be added before clustered rows
        _static_row_added = true;
        auto size_placeholder = _w.write_place_holder<uint32_t>();
        return row_writer(_slice, _w, size_placeholder);
    }

    uint32_t row_count() const {
        return _row_count;
    }

    void finish() {
        _w.set(_count_ph, _row_count);

        // The partition is live. If there are no clustered rows, there
        // must be something live in the static row, which counts as one row.
        _row_count = std::max<uint32_t>(_row_count, 1);

    }

    void retract() {
        _row_count = 0;
        _w.retract(_pos);
    }

    const partition_slice& slice() const {
        return _slice;
    }
};

class result::builder {
    bytes_ostream _w;
    const partition_slice& _slice;
public:
    builder(const partition_slice& slice) : _slice(slice) { }

    // Starts new partition and returns a builder for its contents.
    // Invalidates all previously obtained builders
    partition_writer add_partition(const partition_key& key) {
        auto pos = _w.pos();
        auto count_place_holder = _w.write_place_holder<uint32_t>();
        if (_slice.options.contains<partition_slice::option::send_partition_key>()) {
            _w.write_blob(key);
        }
        return partition_writer(_slice, count_place_holder, pos, _w);
    }

    result build() {
        return result(std::move(_w));
    };

    const partition_slice& slice() const {
        return _slice;
    }
};

}
<|endoftext|>


<commit_before><commit_msg>Already sync not necessary to sync twice<commit_after><|endoftext|>


<commit_before>/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <oboe/AudioStreamBuilder.h>
#include <oboe/Oboe.h>

#include "OboeDebug.h"
#include "QuirksManager.h"

using namespace oboe;

int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
            int32_t requestedSize) {
    if (!OboeGlobals::areWorkaroundsEnabled()) {
        return requestedSize;
    }
    int bottomMargin = kDefaultBottomMarginInBursts;
    int topMargin = kDefaultTopMarginInBursts;
    if (isMMapUsed(stream)) {
        if (stream.getSharingMode() == SharingMode::Exclusive) {
            bottomMargin = getExclusiveBottomMarginInBursts();
            topMargin = getExclusiveTopMarginInBursts();
        }
    } else {
        bottomMargin = kLegacyBottomMarginInBursts;
    }

    int32_t burst = stream.getFramesPerBurst();
    int32_t minSize = bottomMargin * burst;
    int32_t adjustedSize = requestedSize;
    if (adjustedSize < minSize ) {
        adjustedSize = minSize;
    } else {
        int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
        if (adjustedSize > maxSize ) {
            adjustedSize = maxSize;
        }
    }
    return adjustedSize;
}

bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
    bool isSampleRateCompatible =
            builder.getSampleRate() == oboe::Unspecified
            || builder.getSampleRate() == kCommonNativeRate
            || builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
    return builder.getPerformanceMode() == PerformanceMode::LowLatency
            && isSampleRateCompatible
            && builder.getChannelCount() <= kChannelCountStereo;
}

class SamsungDeviceQuirks : public  QuirksManager::DeviceQuirks {
public:
    SamsungDeviceQuirks() {
        std::string arch = getPropertyString("ro.arch");
        isExynos = (arch.rfind("exynos", 0) == 0); // starts with?

        std::string chipname = getPropertyString("ro.hardware.chipname");
        isExynos9810 = (chipname == "exynos9810");
        isExynos990 = (chipname == "exynos990");
        isExynos850 = (chipname == "exynos850");

        mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
    }

    virtual ~SamsungDeviceQuirks() = default;

    int32_t getExclusiveBottomMarginInBursts() const override {
        // TODO Make this conditional on build version when MMAP timing improves.
        return isExynos ? kBottomMarginExynos : kBottomMarginOther;
    }

    int32_t getExclusiveTopMarginInBursts() const override {
        return kTopMargin;
    }

    // See Oboe issues #824 and #1247 for more information.
    bool isMonoMMapActuallyStereo() const override {
        return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed.
    }

    bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
        return DeviceQuirks::isAAudioMMapPossible(builder)
                // Samsung says they use Legacy for Camcorder
                && builder.getInputPreset() != oboe::InputPreset::Camcorder;
    }

    bool isMMapSafe(const AudioStreamBuilder &builder) override {
        const bool isInput = builder.getDirection() == Direction::Input;
        // This detects b/159066712 , S20 LSI has corrupt low latency audio recording
        // and turns off MMAP.
        // See also https://github.com/google/oboe/issues/892
        bool mRecordingCorrupted = isInput
            && isExynos990
            && mBuildChangelist < 19350896;
        return !mRecordingCorrupted;
    }

private:
    // Stay farther away from DSP position on Exynos devices.
    static constexpr int32_t kBottomMarginExynos = 2;
    static constexpr int32_t kBottomMarginOther = 1;
    static constexpr int32_t kTopMargin = 1;
    bool isExynos = false;
    bool isExynos9810 = false;
    bool isExynos990 = false;
    bool isExynos850 = false;
    int mBuildChangelist = 0;
};

QuirksManager::QuirksManager() {
    std::string manufacturer = getPropertyString("ro.product.manufacturer");
    if (manufacturer == "samsung") {
        mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
    } else {
        mDeviceQuirks = std::make_unique<DeviceQuirks>();
    }
}

bool QuirksManager::isConversionNeeded(
        const AudioStreamBuilder &builder,
        AudioStreamBuilder &childBuilder) {
    bool conversionNeeded = false;
    const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
    const bool isInput = builder.getDirection() == Direction::Input;
    const bool isFloat = builder.getFormat() == AudioFormat::Float;

    // There are multiple bugs involving using callback with a specified callback size.
    // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
    // and a specified callback size. It would assert because of a bad buffer size.
    //
    // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
    // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
    // Internally b/161914201#comment25
    //
    // Issue #983: O to R would glitch if the framesPerCallback was too small.
    //
    // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
    // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
    if (OboeGlobals::areWorkaroundsEnabled()
            && builder.willUseAAudio()
            && builder.isDataCallbackSpecified()
            && builder.getFramesPerDataCallback() != 0
            && getSdkVersion() <= __ANDROID_API_R__) {
        LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
        childBuilder.setFramesPerCallback(oboe::Unspecified);
        conversionNeeded = true;
    }

    // If a SAMPLE RATE is specified for low latency then let the native code choose an optimal rate.
    // TODO There may be a problem if the devices supports low latency
    //      at a higher rate than the default.
    if (builder.getSampleRate() != oboe::Unspecified
            && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
            && isLowLatency
            ) {
        childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
        conversionNeeded = true;
    }

    // Data Format
    // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
    if (isFloat
            && isInput
            && builder.isFormatConversionAllowed()
            && isLowLatency
            && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
            ) {
        childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
        conversionNeeded = true;
        LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
    }

    // Add quirk for float output on API <21
    if (isFloat
            && !isInput
            && getSdkVersion() < __ANDROID_API_L__ && builder.isFormatConversionAllowed()) {
        childBuilder.setFormat(AudioFormat::I16);
        conversionNeeded = true;
        LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices, "
             "creating an underlying I16 stream and using format conversion to provide a float "
             "stream", __func__);
    }

    // Channel Count conversions
    if (OboeGlobals::areWorkaroundsEnabled()
            && builder.isChannelConversionAllowed()
            && builder.getChannelCount() == kChannelCountStereo
            && isInput
            && isLowLatency
            && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
            ) {
        // Workaround for heap size regression in O.
        // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
        childBuilder.setChannelCount(kChannelCountMono);
        conversionNeeded = true;
        LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
    } else if (OboeGlobals::areWorkaroundsEnabled()
               && builder.getChannelCount() == kChannelCountMono
               && isInput
               && mDeviceQuirks->isMonoMMapActuallyStereo()
               && builder.willUseAAudio()
               // Note: we might use this workaround on a device that supports
               // MMAP but will use Legacy for this stream.  But this will only happen
               // on devices that have the broken mono.
               && mDeviceQuirks->isAAudioMMapPossible(builder)
               ) {
        // Workaround for mono actually running in stereo mode.
        childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
        conversionNeeded = true;
        LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
    }
    // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
    // phones and they have almost all been updated to 9.0.

    return conversionNeeded;
}

bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
    if (!OboeGlobals::areWorkaroundsEnabled()) return true;
    return mDeviceQuirks->isMMapSafe(builder);
}
<commit_msg>Fix formatting<commit_after>/*
 * Copyright 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include <oboe/AudioStreamBuilder.h>
#include <oboe/Oboe.h>

#include "OboeDebug.h"
#include "QuirksManager.h"

using namespace oboe;

int32_t QuirksManager::DeviceQuirks::clipBufferSize(AudioStream &stream,
            int32_t requestedSize) {
    if (!OboeGlobals::areWorkaroundsEnabled()) {
        return requestedSize;
    }
    int bottomMargin = kDefaultBottomMarginInBursts;
    int topMargin = kDefaultTopMarginInBursts;
    if (isMMapUsed(stream)) {
        if (stream.getSharingMode() == SharingMode::Exclusive) {
            bottomMargin = getExclusiveBottomMarginInBursts();
            topMargin = getExclusiveTopMarginInBursts();
        }
    } else {
        bottomMargin = kLegacyBottomMarginInBursts;
    }

    int32_t burst = stream.getFramesPerBurst();
    int32_t minSize = bottomMargin * burst;
    int32_t adjustedSize = requestedSize;
    if (adjustedSize < minSize ) {
        adjustedSize = minSize;
    } else {
        int32_t maxSize = stream.getBufferCapacityInFrames() - (topMargin * burst);
        if (adjustedSize > maxSize ) {
            adjustedSize = maxSize;
        }
    }
    return adjustedSize;
}

bool QuirksManager::DeviceQuirks::isAAudioMMapPossible(const AudioStreamBuilder &builder) const {
    bool isSampleRateCompatible =
            builder.getSampleRate() == oboe::Unspecified
            || builder.getSampleRate() == kCommonNativeRate
            || builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None;
    return builder.getPerformanceMode() == PerformanceMode::LowLatency
            && isSampleRateCompatible
            && builder.getChannelCount() <= kChannelCountStereo;
}

class SamsungDeviceQuirks : public  QuirksManager::DeviceQuirks {
public:
    SamsungDeviceQuirks() {
        std::string arch = getPropertyString("ro.arch");
        isExynos = (arch.rfind("exynos", 0) == 0); // starts with?

        std::string chipname = getPropertyString("ro.hardware.chipname");
        isExynos9810 = (chipname == "exynos9810");
        isExynos990 = (chipname == "exynos990");
        isExynos850 = (chipname == "exynos850");

        mBuildChangelist = getPropertyInteger("ro.build.changelist", 0);
    }

    virtual ~SamsungDeviceQuirks() = default;

    int32_t getExclusiveBottomMarginInBursts() const override {
        // TODO Make this conditional on build version when MMAP timing improves.
        return isExynos ? kBottomMarginExynos : kBottomMarginOther;
    }

    int32_t getExclusiveTopMarginInBursts() const override {
        return kTopMargin;
    }

    // See Oboe issues #824 and #1247 for more information.
    bool isMonoMMapActuallyStereo() const override {
        return isExynos9810 || isExynos850; // TODO We can make this version specific if it gets fixed.
    }

    bool isAAudioMMapPossible(const AudioStreamBuilder &builder) const override {
        return DeviceQuirks::isAAudioMMapPossible(builder)
                // Samsung says they use Legacy for Camcorder
                && builder.getInputPreset() != oboe::InputPreset::Camcorder;
    }

    bool isMMapSafe(const AudioStreamBuilder &builder) override {
        const bool isInput = builder.getDirection() == Direction::Input;
        // This detects b/159066712 , S20 LSI has corrupt low latency audio recording
        // and turns off MMAP.
        // See also https://github.com/google/oboe/issues/892
        bool mRecordingCorrupted = isInput
            && isExynos990
            && mBuildChangelist < 19350896;
        return !mRecordingCorrupted;
    }

private:
    // Stay farther away from DSP position on Exynos devices.
    static constexpr int32_t kBottomMarginExynos = 2;
    static constexpr int32_t kBottomMarginOther = 1;
    static constexpr int32_t kTopMargin = 1;
    bool isExynos = false;
    bool isExynos9810 = false;
    bool isExynos990 = false;
    bool isExynos850 = false;
    int mBuildChangelist = 0;
};

QuirksManager::QuirksManager() {
    std::string manufacturer = getPropertyString("ro.product.manufacturer");
    if (manufacturer == "samsung") {
        mDeviceQuirks = std::make_unique<SamsungDeviceQuirks>();
    } else {
        mDeviceQuirks = std::make_unique<DeviceQuirks>();
    }
}

bool QuirksManager::isConversionNeeded(
        const AudioStreamBuilder &builder,
        AudioStreamBuilder &childBuilder) {
    bool conversionNeeded = false;
    const bool isLowLatency = builder.getPerformanceMode() == PerformanceMode::LowLatency;
    const bool isInput = builder.getDirection() == Direction::Input;
    const bool isFloat = builder.getFormat() == AudioFormat::Float;

    // There are multiple bugs involving using callback with a specified callback size.
    // Issue #778: O to Q had a problem with Legacy INPUT streams for FLOAT streams
    // and a specified callback size. It would assert because of a bad buffer size.
    //
    // Issue #973: O to R had a problem with Legacy output streams using callback and a specified callback size.
    // An AudioTrack stream could still be running when the AAudio FixedBlockReader was closed.
    // Internally b/161914201#comment25
    //
    // Issue #983: O to R would glitch if the framesPerCallback was too small.
    //
    // Most of these problems were related to Legacy stream. MMAP was OK. But we don't
    // know if we will get an MMAP stream. So, to be safe, just do the conversion in Oboe.
    if (OboeGlobals::areWorkaroundsEnabled()
            && builder.willUseAAudio()
            && builder.isDataCallbackSpecified()
            && builder.getFramesPerDataCallback() != 0
            && getSdkVersion() <= __ANDROID_API_R__) {
        LOGI("QuirksManager::%s() avoid setFramesPerCallback(n>0)", __func__);
        childBuilder.setFramesPerCallback(oboe::Unspecified);
        conversionNeeded = true;
    }

    // If a SAMPLE RATE is specified for low latency then let the native code choose an optimal rate.
    // TODO There may be a problem if the devices supports low latency
    //      at a higher rate than the default.
    if (builder.getSampleRate() != oboe::Unspecified
            && builder.getSampleRateConversionQuality() != SampleRateConversionQuality::None
            && isLowLatency
            ) {
        childBuilder.setSampleRate(oboe::Unspecified); // native API decides the best sample rate
        conversionNeeded = true;
    }

    // Data Format
    // OpenSL ES and AAudio before P do not support FAST path for FLOAT capture.
    if (isFloat
            && isInput
            && builder.isFormatConversionAllowed()
            && isLowLatency
            && (!builder.willUseAAudio() || (getSdkVersion() < __ANDROID_API_P__))
            ) {
        childBuilder.setFormat(AudioFormat::I16); // needed for FAST track
        conversionNeeded = true;
        LOGI("QuirksManager::%s() forcing internal format to I16 for low latency", __func__);
    }

    // Add quirk for float output on API <21
    if (isFloat
            && !isInput
            && getSdkVersion() < __ANDROID_API_L__
            && builder.isFormatConversionAllowed()
            ) {
        childBuilder.setFormat(AudioFormat::I16);
        conversionNeeded = true;
        LOGI("QuirksManager::%s() float was requested but not supported on pre-L devices, "
             "creating an underlying I16 stream and using format conversion to provide a float "
             "stream", __func__);
    }

    // Channel Count conversions
    if (OboeGlobals::areWorkaroundsEnabled()
            && builder.isChannelConversionAllowed()
            && builder.getChannelCount() == kChannelCountStereo
            && isInput
            && isLowLatency
            && (!builder.willUseAAudio() && (getSdkVersion() == __ANDROID_API_O__))
            ) {
        // Workaround for heap size regression in O.
        // b/66967812 AudioRecord does not allow FAST track for stereo capture in O
        childBuilder.setChannelCount(kChannelCountMono);
        conversionNeeded = true;
        LOGI("QuirksManager::%s() using mono internally for low latency on O", __func__);
    } else if (OboeGlobals::areWorkaroundsEnabled()
               && builder.getChannelCount() == kChannelCountMono
               && isInput
               && mDeviceQuirks->isMonoMMapActuallyStereo()
               && builder.willUseAAudio()
               // Note: we might use this workaround on a device that supports
               // MMAP but will use Legacy for this stream.  But this will only happen
               // on devices that have the broken mono.
               && mDeviceQuirks->isAAudioMMapPossible(builder)
               ) {
        // Workaround for mono actually running in stereo mode.
        childBuilder.setChannelCount(kChannelCountStereo); // Use stereo and extract first channel.
        conversionNeeded = true;
        LOGI("QuirksManager::%s() using stereo internally to avoid broken mono", __func__);
    }
    // Note that MMAP does not support mono in 8.1. But that would only matter on Pixel 1
    // phones and they have almost all been updated to 9.0.

    return conversionNeeded;
}

bool QuirksManager::isMMapSafe(AudioStreamBuilder &builder) {
    if (!OboeGlobals::areWorkaroundsEnabled()) return true;
    return mDeviceQuirks->isMMapSafe(builder);
}
<|endoftext|>