<commit_before>#include "SearchForm.hpp"
#include "SceneRegister.hpp"
#include "MapsWithMeForm.hpp"
#include "AppResourceId.h"

#include "../../../map/user_mark.hpp"
#include "../../../map/framework.hpp"
#include "../../../search/result.hpp"
#include "../../../platform/settings.hpp"
#include "../../../platform/tizen_utils.hpp"
#include "../../../base/logging.hpp"

#include <FWeb.h>
#include <FAppApp.h>
#include <FApp.h>
#include "Utils.hpp"
#include "Framework.hpp"
#include "Constants.hpp"

using namespace Tizen::Base;
using namespace Tizen::Ui;
using namespace Tizen::Ui::Controls;
using namespace Tizen::Ui::Scenes;
using namespace Tizen::Base::Collection;
using namespace Tizen::Graphics;
using namespace search;
using namespace consts;

namespace detail
{
typedef vector<String> CategoriesT;
CategoriesT const & GetCategories()
{
  static CategoriesT vr;
  if (vr.empty())
  {
    vr.push_back(GetString(IDS_FOOD));
    vr.push_back(GetString(IDS_SHOP));
    vr.push_back(GetString(IDS_HOTEL));
    vr.push_back(GetString(IDS_TOURISM));
    vr.push_back(GetString(IDS_ENTERTAINMENT));
    vr.push_back(GetString(IDS_ATM));
    vr.push_back(GetString(IDS_BANK));
    vr.push_back(GetString(IDS_TRANSPORT));
    vr.push_back(GetString(IDS_FUEL));
    vr.push_back(GetString(IDS_PARKING));
    vr.push_back(GetString(IDS_PHARMACY));
    vr.push_back(GetString(IDS_HOSPITAL));
    vr.push_back(GetString(IDS_TOILET));
    vr.push_back(GetString(IDS_POST));
    vr.push_back(GetString(IDS_POLICE));
  }
  return vr;
}

CustomItem * CreateFeatureItem(Result const & val, double itemWidth)
{
  String itemText = val.GetString();
  CustomItem * pItem = new CustomItem();

  pItem->Construct(FloatDimension(itemWidth, lstItmHght), LIST_ANNEX_STYLE_NORMAL);
  FloatRectangle imgRect(btwWdth, topHght, imgWdth, imgHght);
  pItem->AddElement(imgRect, 0, *GetBitmap(IDB_SINGLE_RESULT), null, null);
  int txtWdht = itemWidth - btwWdth - imgWdth - backWdth;
  pItem->AddElement(FloatRectangle(btwWdth + imgWdth + btwWdth, 15, txtWdht, imgHght), 1, val.GetString(), mainFontSz, white, white, white);
  pItem->AddElement(FloatRectangle(btwWdth + imgWdth + btwWdth, 60.0f, txtWdht, imgHght), 2, val.GetRegionString(), minorFontSz, white, white, white);
  String feature = val.GetFeatureType();
  pItem->AddElement(FloatRectangle(itemWidth - backWdth, 10, backWdth, imgHght), 3, GetFeature(feature), minorFontSz, gray, gray, gray);
  double lat, lon;
  GetFramework()->GetCurrentPosition(lat, lon);
  double north = 0;
  string distance;
  double azimut;
  GetFramework()->GetDistanceAndAzimut(val.GetFeatureCenter(), lat, lon, north, distance, azimut);
  String dist(distance.c_str());
  pItem->AddElement(FloatRectangle(itemWidth - backWdth, 50, backWdth, imgHght), 4, dist, minorFontSz, gray, gray, gray);

  return pItem;
}

CustomItem * CreateSuggestionItem(String const & val, double itemWidth)
{
  String itemText = val;
  CustomItem * pItem = new CustomItem();

  pItem->Construct(FloatDimension(itemWidth, lstItmHght), LIST_ANNEX_STYLE_NORMAL);
  FloatRectangle imgRect(btwWdth, topHght, imgWdth, imgHght);
  pItem->AddElement(imgRect, 0, *GetBitmap(IDB_SUGGESTION_RESULT), null, null);
  pItem->AddElement(FloatRectangle(btwWdth + imgWdth + btwWdth, topHght, itemWidth, imgHght), 1, itemText, mainFontSz, white, white, white);

  return pItem;
}

} // detail

using namespace detail;

SearchForm::SearchForm()
:m_searchBar(0)
{
}

SearchForm::~SearchForm(void)
{
}

bool SearchForm::Initialize(void)
{
  Construct(IDF_SEARCH_FORM);
  return true;
}

result SearchForm::OnInitializing(void)
{
  m_searchBar = static_cast<SearchBar *>(GetControl(IDC_SEARCHBAR, true));
  m_searchBar->SetMode(SEARCH_BAR_MODE_INPUT);
  m_searchBar->AddActionEventListener(*this);
  m_searchBar->AddTextEventListener(*this);
  m_searchBar->AddKeypadEventListener(*this);

  ListView * pList = static_cast<ListView *>(GetControl(IDC_LISTVIEW, true));
  pList->SetItemProvider(*this);
  pList->AddListViewItemEventListener(*this);
  pList->AddScrollEventListener(*this);

  SetFormBackEventListener(this);

  return E_SUCCESS;
}

void SearchForm::OnActionPerformed(Tizen::Ui::Control const & source, int actionId)
{
  if (actionId == m_searchBar->GetButtonActionId())
  {
    GetFramework()->CancelInteractiveSearch();
    SceneManager * pSceneManager = SceneManager::GetInstance();
    pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT));
  }
}

void SearchForm::OnTextValueChanged(Tizen::Ui::Control const & source)
{
  Search(GetSearchString());
}

void SearchForm::OnSearchResultsReceived(search::Results const & results)
{
  if (results.IsEndMarker())
  {
    if (!results.IsEndedNormal())
      m_curResults.Clear();
    UpdateList();
  }
  else
    m_curResults = results;
}

void SearchForm::OnFormBackRequested(Tizen::Ui::Controls::Form & source)
{
  GetFramework()->CancelInteractiveSearch();
  SceneManager * pSceneManager = SceneManager::GetInstance();
  pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT));
}

ListItemBase * SearchForm::CreateItem (int index, float itemWidth)
{
  if(IsShowCategories())
  {
    return CreateSuggestionItem(GetCategories()[index], itemWidth);
  }
  else
  {
    if (m_curResults.GetCount() == 0)
    {
      String itemText = GetString(IDS_NO_SEARCH_RESULTS_FOUND);
      CustomItem * pItem = new CustomItem();
      pItem->Construct(FloatDimension(itemWidth, lstItmHght), LIST_ANNEX_STYLE_NORMAL);
      pItem->AddElement(FloatRectangle(btwWdth, topHght, itemWidth, imgHght), 0, itemText, mainFontSz, white, white, white);
      return pItem;
    }
    else
    {
      Result const & res = m_curResults.GetResult(index);
      if (res.GetResultType() == Result::RESULT_SUGGESTION)
        return CreateSuggestionItem(res.GetString(), itemWidth);
      else
        return CreateFeatureItem(res, itemWidth);
    }
  }
}

void SearchForm::OnListViewItemStateChanged(Tizen::Ui::Controls::ListView & listView, int index, int elementId, Tizen::Ui::Controls::ListItemStatus status)
{
  if(IsShowCategories())
  {
    m_searchBar->SetText(GetCategories()[index]);
    Search(GetSearchString());
    Invalidate(true);
  }
  else
  {
    if (m_curResults.GetCount() > 0)
    {
      Result res = m_curResults.GetResult(index);
      if (res.GetResultType() == Result::RESULT_SUGGESTION)
      {
        m_searchBar->SetText(res.GetString());
        Search(GetSearchString());
        Invalidate(true);
      }
      else
      {
        GetFramework()->ShowSearchResult(res);
        SceneManager * pSceneManager = SceneManager::GetInstance();
        pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT));
      }
    }
  }
}

void SearchForm::OnScrollPositionChanged(Tizen::Ui::Control & source, int scrollPosition)
{
  m_searchBar->HideKeypad();
}

bool SearchForm::DeleteItem (int index, Tizen::Ui::Controls::ListItemBase * pItem, float itemWidth)
{
  delete pItem;
  return true;
}

int SearchForm::GetItemCount(void)
{
  if (IsShowCategories())
  {
    return GetCategories().size();
  }
  else
  {
    if (m_curResults.GetCount() == 0)
      return 1;
    else
      return m_curResults.GetCount();
  }
}

void SearchForm::UpdateList()
{
  ListView * pList = static_cast<ListView *>(GetControl(IDC_LISTVIEW));
  pList->UpdateList();
  pList->RemoveScrollEventListener(*this);
  pList->ScrollToItem(0);
  pList->AddScrollEventListener(*this);
}

void SearchForm::Search(String const & val)
{
  search::SearchParams params;
  params.m_callback = bind(&SearchForm::OnSearchResultsReceived, this, _1);
  params.m_query = FromTizenString(val);

  Tizen::Locales::LanguageCode language;
  if (m_searchBar->GetCurrentLanguage(language) == E_SUCCESS)
    params.SetInputLanguage(CodeFromISO369_2to_1(GetLanguageCode(language)));
  double lat, lon;
  GetFramework()->GetCurrentPosition(lat, lon);
  params.SetPosition(lat, lon);

  GetFramework()->Search(params);
}

String SearchForm::GetSearchString() const
{
  return m_searchBar->GetText();
}

bool SearchForm::IsShowCategories() const
{
  return GetSearchString().IsEmpty();
}

void SearchForm::OnKeypadActionPerformed (Tizen::Ui::Control & source, Tizen::Ui::KeypadAction keypadAction)
{
  if (keypadAction == KEYPAD_ACTION_SEARCH)
  {
    GetFramework()->ShowAllSearchResults();
    ArrayList * pList = new ArrayList;
    pList->Construct();
    pList->Add(new String(GetSearchString()));
    SceneManager * pSceneManager = SceneManager::GetInstance();
    pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT), pList);
  }
}

void SearchForm::OnSceneActivatedN(const Tizen::Ui::Scenes::SceneId& previousSceneId,
    const Tizen::Ui::Scenes::SceneId& currentSceneId, Tizen::Base::Collection::IList* pArgs)
{
  if (pArgs != null)
  {
    // Come from Main page in resume mode
    if (pArgs->GetCount() == 1)
    {
      String * pSearchText = dynamic_cast<String *>(pArgs->GetAt(0));
      m_searchBar->SetText(*pSearchText);
      Search(GetSearchString());
    }
    pArgs->RemoveAll(true);
    delete pArgs;
  }
}
<commit_msg>[Tizen] Enable interactive search.<commit_after>#include "SearchForm.hpp"
#include "SceneRegister.hpp"
#include "MapsWithMeForm.hpp"
#include "AppResourceId.h"

#include "../../../map/user_mark.hpp"
#include "../../../map/framework.hpp"
#include "../../../search/result.hpp"
#include "../../../platform/settings.hpp"
#include "../../../platform/tizen_utils.hpp"
#include "../../../base/logging.hpp"

#include <FWeb.h>
#include <FAppApp.h>
#include <FApp.h>
#include "Utils.hpp"
#include "Framework.hpp"
#include "Constants.hpp"

using namespace Tizen::Base;
using namespace Tizen::Ui;
using namespace Tizen::Ui::Controls;
using namespace Tizen::Ui::Scenes;
using namespace Tizen::Base::Collection;
using namespace Tizen::Graphics;
using namespace search;
using namespace consts;

namespace detail
{
typedef vector<String> CategoriesT;
CategoriesT const & GetCategories()
{
  static CategoriesT vr;
  if (vr.empty())
  {
    vr.push_back(GetString(IDS_FOOD));
    vr.push_back(GetString(IDS_SHOP));
    vr.push_back(GetString(IDS_HOTEL));
    vr.push_back(GetString(IDS_TOURISM));
    vr.push_back(GetString(IDS_ENTERTAINMENT));
    vr.push_back(GetString(IDS_ATM));
    vr.push_back(GetString(IDS_BANK));
    vr.push_back(GetString(IDS_TRANSPORT));
    vr.push_back(GetString(IDS_FUEL));
    vr.push_back(GetString(IDS_PARKING));
    vr.push_back(GetString(IDS_PHARMACY));
    vr.push_back(GetString(IDS_HOSPITAL));
    vr.push_back(GetString(IDS_TOILET));
    vr.push_back(GetString(IDS_POST));
    vr.push_back(GetString(IDS_POLICE));
  }
  return vr;
}

CustomItem * CreateFeatureItem(Result const & val, double itemWidth)
{
  String itemText = val.GetString();
  CustomItem * pItem = new CustomItem();

  pItem->Construct(FloatDimension(itemWidth, lstItmHght), LIST_ANNEX_STYLE_NORMAL);
  FloatRectangle imgRect(btwWdth, topHght, imgWdth, imgHght);
  pItem->AddElement(imgRect, 0, *GetBitmap(IDB_SINGLE_RESULT), null, null);
  int txtWdht = itemWidth - btwWdth - imgWdth - backWdth;
  pItem->AddElement(FloatRectangle(btwWdth + imgWdth + btwWdth, 15, txtWdht, imgHght), 1, val.GetString(), mainFontSz, white, white, white);
  pItem->AddElement(FloatRectangle(btwWdth + imgWdth + btwWdth, 60.0f, txtWdht, imgHght), 2, val.GetRegionString(), minorFontSz, white, white, white);
  String feature = val.GetFeatureType();
  pItem->AddElement(FloatRectangle(itemWidth - backWdth, 10, backWdth, imgHght), 3, GetFeature(feature), minorFontSz, gray, gray, gray);
  double lat, lon;
  GetFramework()->GetCurrentPosition(lat, lon);
  double north = 0;
  string distance;
  double azimut;
  GetFramework()->GetDistanceAndAzimut(val.GetFeatureCenter(), lat, lon, north, distance, azimut);
  String dist(distance.c_str());
  pItem->AddElement(FloatRectangle(itemWidth - backWdth, 50, backWdth, imgHght), 4, dist, minorFontSz, gray, gray, gray);

  return pItem;
}

CustomItem * CreateSuggestionItem(String const & val, double itemWidth)
{
  String itemText = val;
  CustomItem * pItem = new CustomItem();

  pItem->Construct(FloatDimension(itemWidth, lstItmHght), LIST_ANNEX_STYLE_NORMAL);
  FloatRectangle imgRect(btwWdth, topHght, imgWdth, imgHght);
  pItem->AddElement(imgRect, 0, *GetBitmap(IDB_SUGGESTION_RESULT), null, null);
  pItem->AddElement(FloatRectangle(btwWdth + imgWdth + btwWdth, topHght, itemWidth, imgHght), 1, itemText, mainFontSz, white, white, white);

  return pItem;
}

} // detail

using namespace detail;

SearchForm::SearchForm()
:m_searchBar(0)
{
}

SearchForm::~SearchForm(void)
{
}

bool SearchForm::Initialize(void)
{
  Construct(IDF_SEARCH_FORM);
  return true;
}

result SearchForm::OnInitializing(void)
{
  m_searchBar = static_cast<SearchBar *>(GetControl(IDC_SEARCHBAR, true));
  m_searchBar->SetMode(SEARCH_BAR_MODE_INPUT);
  m_searchBar->AddActionEventListener(*this);
  m_searchBar->AddTextEventListener(*this);
  m_searchBar->AddKeypadEventListener(*this);

  ListView * pList = static_cast<ListView *>(GetControl(IDC_LISTVIEW, true));
  pList->SetItemProvider(*this);
  pList->AddListViewItemEventListener(*this);
  pList->AddScrollEventListener(*this);

  SetFormBackEventListener(this);

  return E_SUCCESS;
}

void SearchForm::OnActionPerformed(Tizen::Ui::Control const & source, int actionId)
{
  if (actionId == m_searchBar->GetButtonActionId())
  {
    GetFramework()->CancelInteractiveSearch();
    SceneManager * pSceneManager = SceneManager::GetInstance();
    pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT));
  }
}

void SearchForm::OnTextValueChanged(Tizen::Ui::Control const & source)
{
  Search(GetSearchString());
}

void SearchForm::OnSearchResultsReceived(search::Results const & results)
{
  if (results.IsEndMarker())
  {
    if (!results.IsEndedNormal())
      m_curResults.Clear();
    UpdateList();
  }
  else
    m_curResults = results;
}

void SearchForm::OnFormBackRequested(Tizen::Ui::Controls::Form & source)
{
  GetFramework()->CancelInteractiveSearch();
  SceneManager * pSceneManager = SceneManager::GetInstance();
  pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT));
}

ListItemBase * SearchForm::CreateItem (int index, float itemWidth)
{
  if(IsShowCategories())
  {
    return CreateSuggestionItem(GetCategories()[index], itemWidth);
  }
  else
  {
    if (m_curResults.GetCount() == 0)
    {
      String itemText = GetString(IDS_NO_SEARCH_RESULTS_FOUND);
      CustomItem * pItem = new CustomItem();
      pItem->Construct(FloatDimension(itemWidth, lstItmHght), LIST_ANNEX_STYLE_NORMAL);
      pItem->AddElement(FloatRectangle(btwWdth, topHght, itemWidth, imgHght), 0, itemText, mainFontSz, white, white, white);
      return pItem;
    }
    else
    {
      Result const & res = m_curResults.GetResult(index);
      if (res.GetResultType() == Result::RESULT_SUGGESTION)
        return CreateSuggestionItem(res.GetString(), itemWidth);
      else
        return CreateFeatureItem(res, itemWidth);
    }
  }
}

void SearchForm::OnListViewItemStateChanged(Tizen::Ui::Controls::ListView & listView, int index, int elementId, Tizen::Ui::Controls::ListItemStatus status)
{
  if(IsShowCategories())
  {
    m_searchBar->SetText(GetCategories()[index]);
    Search(GetSearchString());
    Invalidate(true);
  }
  else
  {
    if (m_curResults.GetCount() > 0)
    {
      Result res = m_curResults.GetResult(index);
      if (res.GetResultType() == Result::RESULT_SUGGESTION)
      {
        m_searchBar->SetText(res.GetString());
        Search(GetSearchString());
        Invalidate(true);
      }
      else
      {
        GetFramework()->ShowSearchResult(res);
        SceneManager * pSceneManager = SceneManager::GetInstance();
        pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT));
      }
    }
  }
}

void SearchForm::OnScrollPositionChanged(Tizen::Ui::Control & source, int scrollPosition)
{
  m_searchBar->HideKeypad();
}

bool SearchForm::DeleteItem (int index, Tizen::Ui::Controls::ListItemBase * pItem, float itemWidth)
{
  delete pItem;
  return true;
}

int SearchForm::GetItemCount(void)
{
  if (IsShowCategories())
  {
    return GetCategories().size();
  }
  else
  {
    if (m_curResults.GetCount() == 0)
      return 1;
    else
      return m_curResults.GetCount();
  }
}

void SearchForm::UpdateList()
{
  ListView * pList = static_cast<ListView *>(GetControl(IDC_LISTVIEW));
  pList->UpdateList();
  pList->RemoveScrollEventListener(*this);
  pList->ScrollToItem(0);
  pList->AddScrollEventListener(*this);
}

void SearchForm::Search(String const & val)
{
  search::SearchParams params;
  params.m_callback = bind(&SearchForm::OnSearchResultsReceived, this, _1);
  params.m_query = FromTizenString(val);

  Tizen::Locales::LanguageCode language;
  if (m_searchBar->GetCurrentLanguage(language) == E_SUCCESS)
    params.SetInputLanguage(CodeFromISO369_2to_1(GetLanguageCode(language)));
  double lat, lon;
  GetFramework()->GetCurrentPosition(lat, lon);
  params.SetPosition(lat, lon);

  GetFramework()->Search(params);
}

String SearchForm::GetSearchString() const
{
  return m_searchBar->GetText();
}

bool SearchForm::IsShowCategories() const
{
  return GetSearchString().IsEmpty();
}

void SearchForm::OnKeypadActionPerformed (Tizen::Ui::Control & source, Tizen::Ui::KeypadAction keypadAction)
{
  if (keypadAction == KEYPAD_ACTION_SEARCH)
  {
    search::SearchParams params;
    params.m_query = FromTizenString(GetSearchString());
    Tizen::Locales::LanguageCode language;
    if (m_searchBar->GetCurrentLanguage(language) == E_SUCCESS)
      params.SetInputLanguage(CodeFromISO369_2to_1(GetLanguageCode(language)));
    double lat, lon;
    GetFramework()->GetCurrentPosition(lat, lon);
    params.SetPosition(lat, lon);

    GetFramework()->StartInteractiveSearch(params);
    ArrayList * pList = new ArrayList;
    pList->Construct();
    pList->Add(new String(GetSearchString()));
    SceneManager * pSceneManager = SceneManager::GetInstance();
    pSceneManager->GoBackward(BackwardSceneTransition(SCENE_TRANSITION_ANIMATION_TYPE_RIGHT), pList);
  }
}

void SearchForm::OnSceneActivatedN(const Tizen::Ui::Scenes::SceneId& previousSceneId,
    const Tizen::Ui::Scenes::SceneId& currentSceneId, Tizen::Base::Collection::IList* pArgs)
{
  if (pArgs != null)
  {
    // Come from Main page in resume mode
    if (pArgs->GetCount() == 1)
    {
      String * pSearchText = dynamic_cast<String *>(pArgs->GetAt(0));
      m_searchBar->SetText(*pSearchText);
      Search(GetSearchString());
    }
    pArgs->RemoveAll(true);
    delete pArgs;
  }
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2005 Tommi Maekitalo
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <tntdb/postgresql/impl/connection.h>
#include <tntdb/postgresql/impl/result.h>
#include <tntdb/postgresql/impl/statement.h>
#include <tntdb/postgresql/error.h>
#include <tntdb/result.h>
#include <tntdb/statement.h>
#include <cxxtools/log.h>
#include <new>
#include <sstream>

log_define("tntdb.postgresql.connection")

namespace tntdb
{
  namespace postgresql
  {
    Connection::Connection(const char* conninfo)
      : transactionActive(0)
    {
      log_debug("PQconnectdb(\"" << conninfo << "\")");

      conn = PQconnectdb(conninfo);
      if (conn == 0)
        throw std::bad_alloc();

      if (PQstatus(conn) == CONNECTION_BAD )
        throw PgConnError("PQconnectdb", conn);
    }

    Connection::~Connection()
    {
      if (conn)
      {
        clearStatementCache();

        log_debug("PQfinish(" << conn << ")");
        PQfinish(conn);
      }
    }

    void Connection::beginTransaction()
    {
      if (transactionActive == 0)
        execute("BEGIN");
      ++transactionActive;
    }

    void Connection::commitTransaction()
    {
      if (transactionActive == 0 || --transactionActive == 0)
        execute("COMMIT");
    }

    void Connection::rollbackTransaction()
    {
      if (transactionActive == 0 || --transactionActive == 0)
        execute("ROLLBACK");
    }

    Connection::size_type Connection::execute(const std::string& query)
    {
      log_debug("execute(\"" << query << "\")");

      log_debug("PQexec(" << conn << ", \"" << query << "\")");
      PGresult* result = PQexec(conn, query.c_str());
      if (isError(result))
      {
        log_error(PQresultErrorMessage(result));
        throw PgSqlError(query, "PQexec", result, true);
      }

      std::istringstream tuples(PQcmdTuples(result));
      Connection::size_type ret = 0;
      tuples >> ret;

      log_debug("PQclear(" << result << ')');
      PQclear(result);

      return ret;
    }

    tntdb::Result Connection::select(const std::string& query)
    {
      log_debug("select(\"" << query << "\")");

      log_debug("PQexec(" << conn << ", \"" << query << "\")");
      PGresult* result = PQexec(conn, query.c_str());
      if (isError(result))
      {
        log_error(PQresultErrorMessage(result));
        throw PgSqlError(query, "PQexec", result, true);
      }

      return tntdb::Result(new Result(tntdb::Connection(this), result));
    }

    Row Connection::selectRow(const std::string& query)
    {
      log_debug("selectRow(\"" << query << "\")");
      tntdb::Result result = select(query);
      if (result.empty())
        throw NotFound();

      return result.getRow(0);
    }

    Value Connection::selectValue(const std::string& query)
    {
      log_debug("selectValue(\"" << query << "\")");
      Row t = selectRow(query);
      if (t.empty())
        throw NotFound();

      return t.getValue(0);
    }

    tntdb::Statement Connection::prepare(const std::string& query)
    {
      log_debug("prepare(\"" << query << "\")");
      return tntdb::Statement(new Statement(this, query));
    }

    bool Connection::ping()
    {
      log_debug("ping()");
      try
      {
        select("select 1");
        return true;
      }
      catch (const PgError&)
      {
        return false;
      }
    }

    long Connection::lastInsertId(const std::string& name)
    {
      if (!currvalStmt)
        currvalStmt = prepare("select currval(:name)");

      long ret;
      currvalStmt.set("name", name)
        .selectValue()
        .get(ret);

      return ret;
    }

  }
}
<commit_msg>fix warning in postgresql driver about failed deallocation of statement on close connection when lastInserId was used<commit_after>/*
 * Copyright (C) 2005 Tommi Maekitalo
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * As a special exception, you may use this file as part of a free
 * software library without restriction. Specifically, if other files
 * instantiate templates or use macros or inline functions from this
 * file, or you compile this file and link it with other files to
 * produce an executable, this file does not by itself cause the
 * resulting executable to be covered by the GNU General Public
 * License. This exception does not however invalidate any other
 * reasons why the executable file might be covered by the GNU Library
 * General Public License.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include <tntdb/postgresql/impl/connection.h>
#include <tntdb/postgresql/impl/result.h>
#include <tntdb/postgresql/impl/statement.h>
#include <tntdb/postgresql/error.h>
#include <tntdb/result.h>
#include <tntdb/statement.h>
#include <cxxtools/log.h>
#include <new>
#include <sstream>

log_define("tntdb.postgresql.connection")

namespace tntdb
{
  namespace postgresql
  {
    Connection::Connection(const char* conninfo)
      : transactionActive(0)
    {
      log_debug("PQconnectdb(\"" << conninfo << "\")");

      conn = PQconnectdb(conninfo);
      if (conn == 0)
        throw std::bad_alloc();

      if (PQstatus(conn) == CONNECTION_BAD )
        throw PgConnError("PQconnectdb", conn);
    }

    Connection::~Connection()
    {
      if (conn)
      {
        clearStatementCache();
        currvalStmt = tntdb::Statement();

        log_debug("PQfinish(" << conn << ")");
        PQfinish(conn);
      }
    }

    void Connection::beginTransaction()
    {
      if (transactionActive == 0)
        execute("BEGIN");
      ++transactionActive;
    }

    void Connection::commitTransaction()
    {
      if (transactionActive == 0 || --transactionActive == 0)
        execute("COMMIT");
    }

    void Connection::rollbackTransaction()
    {
      if (transactionActive == 0 || --transactionActive == 0)
        execute("ROLLBACK");
    }

    Connection::size_type Connection::execute(const std::string& query)
    {
      log_debug("execute(\"" << query << "\")");

      log_debug("PQexec(" << conn << ", \"" << query << "\")");
      PGresult* result = PQexec(conn, query.c_str());
      if (isError(result))
      {
        log_error(PQresultErrorMessage(result));
        throw PgSqlError(query, "PQexec", result, true);
      }

      std::istringstream tuples(PQcmdTuples(result));
      Connection::size_type ret = 0;
      tuples >> ret;

      log_debug("PQclear(" << result << ')');
      PQclear(result);

      return ret;
    }

    tntdb::Result Connection::select(const std::string& query)
    {
      log_debug("select(\"" << query << "\")");

      log_debug("PQexec(" << conn << ", \"" << query << "\")");
      PGresult* result = PQexec(conn, query.c_str());
      if (isError(result))
      {
        log_error(PQresultErrorMessage(result));
        throw PgSqlError(query, "PQexec", result, true);
      }

      return tntdb::Result(new Result(tntdb::Connection(this), result));
    }

    Row Connection::selectRow(const std::string& query)
    {
      log_debug("selectRow(\"" << query << "\")");
      tntdb::Result result = select(query);
      if (result.empty())
        throw NotFound();

      return result.getRow(0);
    }

    Value Connection::selectValue(const std::string& query)
    {
      log_debug("selectValue(\"" << query << "\")");
      Row t = selectRow(query);
      if (t.empty())
        throw NotFound();

      return t.getValue(0);
    }

    tntdb::Statement Connection::prepare(const std::string& query)
    {
      log_debug("prepare(\"" << query << "\")");
      return tntdb::Statement(new Statement(this, query));
    }

    bool Connection::ping()
    {
      log_debug("ping()");
      try
      {
        select("select 1");
        return true;
      }
      catch (const PgError&)
      {
        return false;
      }
    }

    long Connection::lastInsertId(const std::string& name)
    {
      if (!currvalStmt)
        currvalStmt = prepare("select currval(:name)");

      long ret;
      currvalStmt.set("name", name)
        .selectValue()
        .get(ret);

      return ret;
    }

  }
}
<|endoftext|>


<commit_before>/* openssl.cpp
 * Copyright (C) 2003-2005 Tommi Maekitalo
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "tnt/openssl.h"
#include <cxxtools/thread.h>
#include <openssl/err.h>
#include <cxxtools/log.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/poll.h>

log_define("tntnet.ssl")

namespace tnt
{
  static void checkSslError()
  {
    log_debug("ERR_get_error");
    unsigned long code = ERR_get_error();
    if (code != 0)
    {
      char buffer[120];
      log_debug("ERR_error_string");
      if (ERR_error_string(code, buffer))
      {
        log_debug("SSL-Error " << code << ": \"" << buffer << '"');
        throw OpensslException(buffer, code);
      }
      else
      {
        log_debug("unknown SSL-Error " << code);
        throw OpensslException("unknown SSL-Error", code);
      }
    }
  }

  static cxxtools::Mutex *openssl_mutex;

  static unsigned long pthreads_thread_id()
  {
    return (unsigned long)pthread_self();
  }

  static void pthreads_locking_callback(int mode, int n, const char *file,
    int line)
  {
    /*
    log_debug("pthreads_locking_callback " << CRYPTO_thread_id()
      << " n=" << n
      << " mode=" << ((mode & CRYPTO_LOCK) ? 'l' : 'u')
      << ' ' << file << ':' << line); */

    if (mode & CRYPTO_LOCK)
      openssl_mutex[n].lock();
    else
      openssl_mutex[n].unlock();
  }

  static void thread_setup(void)
  {
    openssl_mutex = new cxxtools::Mutex[CRYPTO_num_locks()];

    CRYPTO_set_id_callback(pthreads_thread_id);
    CRYPTO_set_locking_callback(pthreads_locking_callback);
  }

  static cxxtools::Mutex mutex;
  static void openssl_init()
  {
    static bool initialized = false;

    if (!initialized)
    {
      cxxtools::MutexLock lock(mutex);
      if (!initialized)
      {
        log_debug("SSL_load_error_strings");
        SSL_load_error_strings();
        log_debug("SSL_library_init");
        SSL_library_init();

        checkSslError();
        thread_setup();
        initialized = true;
      }
    }
  }

  void OpensslServer::installCertificates(const char* certificateFile, const char* privateKeyFile)
  {
    log_debug("use certificate file " << certificateFile);
    if (SSL_CTX_use_certificate_file(ctx, certificateFile, SSL_FILETYPE_PEM) <= 0)
      checkSslError();

    log_debug("use private key file " << privateKeyFile);
    if (SSL_CTX_use_PrivateKey_file(ctx, privateKeyFile, SSL_FILETYPE_PEM) <= 0)
      checkSslError();

    log_debug("check private key");
    if (!SSL_CTX_check_private_key(ctx))
      throw OpensslException("private key does not match the certificate public key", 0);

    log_debug("private key ok");
  }

  OpensslServer::OpensslServer(const char* certificateFile)
  {
    openssl_init();

    log_debug("SSL_CTX_new(SSLv23_server_method())");
    ctx = SSL_CTX_new(SSLv23_server_method());
    checkSslError();

    installCertificates(certificateFile, certificateFile);
  }

  OpensslServer::OpensslServer(const char* certificateFile, const char* privateKeyFile)
  {
    openssl_init();

    log_debug("SSL_CTX_new(SSLv23_server_method())");
    ctx = SSL_CTX_new(SSLv23_server_method());
    checkSslError();

    installCertificates(certificateFile, privateKeyFile);
  }

  OpensslServer::~OpensslServer()
  {
    if (ctx)
    {
      log_debug("SSL_CTX_free(ctx)");
      SSL_CTX_free(ctx);
    }
  }

  //////////////////////////////////////////////////////////////////////
  // OpensslStream
  //
  OpensslStream::OpensslStream()
    : ssl(0)
  {
    openssl_init();
  }

  OpensslStream::OpensslStream(const OpensslServer& server)
    : ssl(0)
  {
    openssl_init();
    accept(server);
  }

  OpensslStream::~OpensslStream()
  {
    if (ssl)
    {
      try
      {
        shutdown();
      }
      catch (const std::exception& e)
      {
        log_debug("error closing ssl-connection: " << e.what());
      }

      log_debug("SSL_free(" << ssl << ')');
      SSL_free(ssl);
    }
  }

  void OpensslStream::accept(const OpensslServer& server)
  {
    log_debug("accept");
    Stream::accept(server);

    log_debug("tcp-connection established - build ssltunnel");

    log_debug("SSL_new(" << server.getSslContext() << ')');
    ssl = SSL_new( server.getSslContext() );
    checkSslError();

    log_debug("SSL_set_fd(" << ssl << ", " << getFd() << ')');
    SSL_set_fd(ssl, getFd());

    log_debug("SSL_set_accept_state(" << ssl << ')');
    SSL_set_accept_state(ssl);
  }

  int OpensslStream::sslRead(char* buffer, int bufsize) const
  {
    // I had crashes without this (and the lock in sslWrite) lock:
    // openssl should be thread-safe, with the installed callbacks, but I did not
    // get it working
    cxxtools::MutexLock lock(mutex);

    log_debug("read");

    int n;
    int err;

    if (getTimeout() < 0)
    {
      // blocking
      do
      {
        log_debug("SSL_read(" << ssl << ", buffer, " << bufsize << ") (blocking)");
        n = ::SSL_read(ssl, buffer, bufsize);
      } while (n <= 0 &&
                ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
               || err == SSL_ERROR_WANT_WRITE));
      checkSslError();
    }
    else
    {
      // non-blocking/with timeout

      // try read
      log_debug("SSL_read(" << ssl << ", buffer, " << bufsize << ')');
      n = ::SSL_read(ssl, buffer, bufsize);
      log_debug("ssl-read => " << n);

      if (n > 0)
        return n;

      log_debug("SSL_get_error(" << ssl << ", " << n << ')');
      if ((err = SSL_get_error(ssl, n)) != SSL_ERROR_WANT_READ
       && err != SSL_ERROR_WANT_WRITE)
        checkSslError();

      if (getTimeout() == 0)
      {
        log_debug("read-timeout");
        throw cxxtools::net::Timeout();
      }

      // no read, timeout > 0 - poll
      do
      {
        poll(SSL_get_error(ssl, n) == SSL_ERROR_WANT_WRITE
                    ? POLLIN|POLLOUT : POLLIN);

        log_debug("SSL_read(" << ssl << ", buffer, " << bufsize << ')');
        n = ::SSL_read(ssl, buffer, bufsize);
        log_debug("SSL_read returns " << n);
        checkSslError();

      } while (n < 0
         && ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
          || err == SSL_ERROR_WANT_WRITE
          || err == SSL_ERROR_SYSCALL && errno == EAGAIN));
    }
    return n;
  }

  int OpensslStream::sslWrite(const char* buffer, int bufsize) const
  {
    // I had crashes without this (and the lock in sslRead) lock:
    // openssl should be thread-safe, with the installed callbacks, but I did not
    // get it working
    cxxtools::MutexLock lock(mutex);

    int n = 0;
    int s = bufsize;

    while (true)
    {
      log_debug("SSL_write(" << ssl << ", buffer, " << s << ')');
      n = SSL_write(ssl, buffer, s);
      checkSslError();

      int err;
      if (n > 0)
      {
        buffer += n;
        s -= n;
      }
      else if (n < 0
              && (err = SSL_get_error(ssl, n)) != SSL_ERROR_WANT_READ
              && err != SSL_ERROR_WANT_WRITE
              && (err != SSL_ERROR_SYSCALL || errno != EAGAIN))
      {
        log_debug("error " << err << " occured in SSL_write; n=" << n);
        throw OpensslException("error from TLS/SSL I/O operation", err);
      }

      if (s <= 0)
        break;

      poll(SSL_get_error(ssl, n) == SSL_ERROR_WANT_WRITE
                  ? POLLIN|POLLOUT : POLLIN);
    }

    log_debug("OpensslStream::sslWrite returns " << bufsize);
    return bufsize;
  }

  void OpensslStream::shutdown() const
  {
    cxxtools::MutexLock lock(mutex);

    int n, err;
    if (getTimeout() < 0)
    {
      // blocking
      do
      {
        log_debug("shutdown unbuffered");
        n = ::SSL_shutdown(ssl);
      } while (n <= 0 &&
                ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
              || err == SSL_ERROR_WANT_WRITE));
      checkSslError();
    }
    else
    {
      // non-blocking/with timeout

      // try read
      log_debug("SSL_shutdown(" << ssl << ')');
      n = ::SSL_shutdown(ssl);
      log_debug("ssl-shutdown => " << n);

      log_debug("SSL_get_error(" << ssl << ", " << n << ')');
      if ((err = SSL_get_error(ssl, n)) != SSL_ERROR_WANT_READ
       && err != SSL_ERROR_WANT_WRITE)
        checkSslError();

      if (getTimeout() == 0)
      {
        log_debug("shutdown-timeout");
        throw cxxtools::net::Timeout();
      }

      do
      {
        log_debug("poll");
        poll(err == SSL_ERROR_WANT_WRITE ? POLLIN|POLLOUT : POLLIN);

        log_debug("SSL_shutdown(" << ssl << ')');
        n = ::SSL_shutdown(ssl);
        log_debug("SSL_shutdown returns " << n);

        checkSslError();

      } while (n <= 0
         && ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
          || err == SSL_ERROR_WANT_WRITE));
    }
  }

  //////////////////////////////////////////////////////////////////////
  // openssl_streambuf
  //
  openssl_streambuf::openssl_streambuf(OpensslStream& stream, unsigned bufsize, int timeout)
    : m_stream(stream),
      m_buffer(new char_type[bufsize]),
      m_bufsize(bufsize)
  {
    setTimeout(timeout);
  }

  openssl_streambuf::int_type openssl_streambuf::overflow(openssl_streambuf::int_type c)
  {
    try
    {
      if (pptr() != pbase())
      {
        int n = m_stream.sslWrite(pbase(), pptr() - pbase());
        if (n <= 0)
          return traits_type::eof();
      }

      setp(m_buffer, m_buffer + m_bufsize);
      if (c != traits_type::eof())
      {
        *pptr() = (char_type)c;
        pbump(1);
      }

      return 0;
    }
    catch (const std::exception& e)
    {
      log_error("error int openssl_streambuf::overflow: " << e.what());
      return traits_type::eof();
    }
  }

  openssl_streambuf::int_type openssl_streambuf::underflow()
  {
    int n = m_stream.sslRead(m_buffer, m_bufsize);
    if (n <= 0)
      return traits_type::eof();

    setg(m_buffer, m_buffer, m_buffer + n);
    return (int_type)(unsigned char)m_buffer[0];
  }

  int openssl_streambuf::sync()
  {
    try
    {
      if (pptr() != pbase())
      {
        int n = m_stream.sslWrite(pbase(), pptr() - pbase());
        if (n <= 0)
          return -1;
        else
          setp(m_buffer, m_buffer + m_bufsize);
      }
      return 0;
    }
    catch (const std::exception& e)
    {
      log_error("error in openssl_streambuf::sync(): " << e.what());
      return traits_type::eof();
    }
  }

  //////////////////////////////////////////////////////////////////////
  // openssl_iostream
  //
}
<commit_msg>another fix in error handling of openssl<commit_after>/* openssl.cpp
 * Copyright (C) 2003-2005 Tommi Maekitalo
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

#include "tnt/openssl.h"
#include <cxxtools/thread.h>
#include <openssl/err.h>
#include <cxxtools/log.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/poll.h>

log_define("tntnet.ssl")

namespace tnt
{
  static void checkSslError()
  {
    log_debug("ERR_get_error");
    unsigned long code = ERR_get_error();
    if (code != 0)
    {
      char buffer[120];
      log_debug("ERR_error_string");
      if (ERR_error_string(code, buffer))
      {
        log_debug("SSL-Error " << code << ": \"" << buffer << '"');
        throw OpensslException(buffer, code);
      }
      else
      {
        log_debug("unknown SSL-Error " << code);
        throw OpensslException("unknown SSL-Error", code);
      }
    }
  }

  static cxxtools::Mutex *openssl_mutex;

  static unsigned long pthreads_thread_id()
  {
    return (unsigned long)pthread_self();
  }

  static void pthreads_locking_callback(int mode, int n, const char *file,
    int line)
  {
    /*
    log_debug("pthreads_locking_callback " << CRYPTO_thread_id()
      << " n=" << n
      << " mode=" << ((mode & CRYPTO_LOCK) ? 'l' : 'u')
      << ' ' << file << ':' << line); */

    if (mode & CRYPTO_LOCK)
      openssl_mutex[n].lock();
    else
      openssl_mutex[n].unlock();
  }

  static void thread_setup(void)
  {
    openssl_mutex = new cxxtools::Mutex[CRYPTO_num_locks()];

    CRYPTO_set_id_callback(pthreads_thread_id);
    CRYPTO_set_locking_callback(pthreads_locking_callback);
  }

  static cxxtools::Mutex mutex;
  static void openssl_init()
  {
    static bool initialized = false;

    if (!initialized)
    {
      cxxtools::MutexLock lock(mutex);
      if (!initialized)
      {
        log_debug("SSL_load_error_strings");
        SSL_load_error_strings();
        log_debug("SSL_library_init");
        SSL_library_init();

        checkSslError();
        thread_setup();
        initialized = true;
      }
    }
  }

  void OpensslServer::installCertificates(const char* certificateFile, const char* privateKeyFile)
  {
    log_debug("use certificate file " << certificateFile);
    if (SSL_CTX_use_certificate_file(ctx, certificateFile, SSL_FILETYPE_PEM) <= 0)
      checkSslError();

    log_debug("use private key file " << privateKeyFile);
    if (SSL_CTX_use_PrivateKey_file(ctx, privateKeyFile, SSL_FILETYPE_PEM) <= 0)
      checkSslError();

    log_debug("check private key");
    if (!SSL_CTX_check_private_key(ctx))
      throw OpensslException("private key does not match the certificate public key", 0);

    log_debug("private key ok");
  }

  OpensslServer::OpensslServer(const char* certificateFile)
  {
    openssl_init();

    log_debug("SSL_CTX_new(SSLv23_server_method())");
    ctx = SSL_CTX_new(SSLv23_server_method());
    checkSslError();

    installCertificates(certificateFile, certificateFile);
  }

  OpensslServer::OpensslServer(const char* certificateFile, const char* privateKeyFile)
  {
    openssl_init();

    log_debug("SSL_CTX_new(SSLv23_server_method())");
    ctx = SSL_CTX_new(SSLv23_server_method());
    checkSslError();

    installCertificates(certificateFile, privateKeyFile);
  }

  OpensslServer::~OpensslServer()
  {
    if (ctx)
    {
      log_debug("SSL_CTX_free(ctx)");
      SSL_CTX_free(ctx);
    }
  }

  //////////////////////////////////////////////////////////////////////
  // OpensslStream
  //
  OpensslStream::OpensslStream()
    : ssl(0)
  {
    openssl_init();
  }

  OpensslStream::OpensslStream(const OpensslServer& server)
    : ssl(0)
  {
    openssl_init();
    accept(server);
  }

  OpensslStream::~OpensslStream()
  {
    if (ssl)
    {
      try
      {
        shutdown();
      }
      catch (const std::exception& e)
      {
        log_debug("error closing ssl-connection: " << e.what());
      }

      log_debug("SSL_free(" << ssl << ')');
      SSL_free(ssl);
    }
  }

  void OpensslStream::accept(const OpensslServer& server)
  {
    log_debug("accept");
    Stream::accept(server);

    log_debug("tcp-connection established - build ssltunnel");

    log_debug("SSL_new(" << server.getSslContext() << ')');
    ssl = SSL_new( server.getSslContext() );
    checkSslError();

    log_debug("SSL_set_fd(" << ssl << ", " << getFd() << ')');
    SSL_set_fd(ssl, getFd());

    log_debug("SSL_set_accept_state(" << ssl << ')');
    SSL_set_accept_state(ssl);
  }

  int OpensslStream::sslRead(char* buffer, int bufsize) const
  {
    // I had crashes without this (and the lock in sslWrite) lock:
    // openssl should be thread-safe, with the installed callbacks, but I did not
    // get it working
    cxxtools::MutexLock lock(mutex);

    log_debug("read");

    int n;
    int err;

    if (getTimeout() < 0)
    {
      // blocking
      do
      {
        log_debug("SSL_read(" << ssl << ", buffer, " << bufsize << ") (blocking)");
        n = ::SSL_read(ssl, buffer, bufsize);
      } while (n <= 0 &&
                ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
               || err == SSL_ERROR_WANT_WRITE));
      checkSslError();
    }
    else
    {
      // non-blocking/with timeout

      // try read
      log_debug("SSL_read(" << ssl << ", buffer, " << bufsize << ')');
      n = ::SSL_read(ssl, buffer, bufsize);
      log_debug("ssl-read => " << n);

      if (n > 0)
        return n;

      log_debug("SSL_get_error(" << ssl << ", " << n << ')');
      if ((err = SSL_get_error(ssl, n)) != SSL_ERROR_WANT_READ
       && err != SSL_ERROR_WANT_WRITE)
        checkSslError();

      if (getTimeout() == 0)
      {
        log_debug("read-timeout");
        throw cxxtools::net::Timeout();
      }

      // no read, timeout > 0 - poll
      do
      {
        poll(SSL_get_error(ssl, n) == SSL_ERROR_WANT_WRITE
                    ? POLLIN|POLLOUT : POLLIN);

        log_debug("SSL_read(" << ssl << ", buffer, " << bufsize << ')');
        n = ::SSL_read(ssl, buffer, bufsize);
        log_debug("SSL_read returns " << n);
        checkSslError();

      } while (n < 0
         && ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
          || err == SSL_ERROR_WANT_WRITE
          || err == SSL_ERROR_SYSCALL && errno == EAGAIN));
    }
    return n;
  }

  int OpensslStream::sslWrite(const char* buffer, int bufsize) const
  {
    // I had crashes without this (and the lock in sslRead) lock:
    // openssl should be thread-safe, with the installed callbacks, but I did not
    // get it working
    cxxtools::MutexLock lock(mutex);

    int n = 0;
    int s = bufsize;

    while (true)
    {
      log_debug("SSL_write(" << ssl << ", buffer, " << s << ')');
      n = SSL_write(ssl, buffer, s);
      checkSslError();

      int err;
      if (n > 0)
      {
        buffer += n;
        s -= n;
      }
      else if (n < 0
              && (err = SSL_get_error(ssl, n)) != SSL_ERROR_WANT_READ
              && err != SSL_ERROR_WANT_WRITE
              && (err != SSL_ERROR_SYSCALL || errno != EAGAIN))
      {
        log_debug("error " << err << " occured in SSL_write; n=" << n);
        throw OpensslException("error from TLS/SSL I/O operation", err);
      }

      if (s <= 0)
        break;

      poll(SSL_get_error(ssl, n) == SSL_ERROR_WANT_READ
                  ? POLLIN : POLLIN|POLLOUT);
    }

    log_debug("OpensslStream::sslWrite returns " << bufsize);
    return bufsize;
  }

  void OpensslStream::shutdown() const
  {
    cxxtools::MutexLock lock(mutex);

    int n, err;
    if (getTimeout() < 0)
    {
      // blocking
      do
      {
        log_debug("shutdown unbuffered");
        n = ::SSL_shutdown(ssl);
      } while (n <= 0 &&
                ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
              || err == SSL_ERROR_WANT_WRITE));
      checkSslError();
    }
    else
    {
      // non-blocking/with timeout

      // try read
      log_debug("SSL_shutdown(" << ssl << ')');
      n = ::SSL_shutdown(ssl);
      log_debug("ssl-shutdown => " << n);

      log_debug("SSL_get_error(" << ssl << ", " << n << ')');
      if ((err = SSL_get_error(ssl, n)) != SSL_ERROR_WANT_READ
       && err != SSL_ERROR_WANT_WRITE)
        checkSslError();

      if (getTimeout() == 0)
      {
        log_debug("shutdown-timeout");
        throw cxxtools::net::Timeout();
      }

      do
      {
        log_debug("poll");
        poll(err == SSL_ERROR_WANT_WRITE ? POLLIN|POLLOUT : POLLIN);

        log_debug("SSL_shutdown(" << ssl << ')');
        n = ::SSL_shutdown(ssl);
        log_debug("SSL_shutdown returns " << n);

        checkSslError();

      } while (n <= 0
         && ((err = SSL_get_error(ssl, n)) == SSL_ERROR_WANT_READ
          || err == SSL_ERROR_WANT_WRITE));
    }
  }

  //////////////////////////////////////////////////////////////////////
  // openssl_streambuf
  //
  openssl_streambuf::openssl_streambuf(OpensslStream& stream, unsigned bufsize, int timeout)
    : m_stream(stream),
      m_buffer(new char_type[bufsize]),
      m_bufsize(bufsize)
  {
    setTimeout(timeout);
  }

  openssl_streambuf::int_type openssl_streambuf::overflow(openssl_streambuf::int_type c)
  {
    try
    {
      if (pptr() != pbase())
      {
        int n = m_stream.sslWrite(pbase(), pptr() - pbase());
        if (n <= 0)
          return traits_type::eof();
      }

      setp(m_buffer, m_buffer + m_bufsize);
      if (c != traits_type::eof())
      {
        *pptr() = (char_type)c;
        pbump(1);
      }

      return 0;
    }
    catch (const std::exception& e)
    {
      log_error("error int openssl_streambuf::overflow: " << e.what());
      return traits_type::eof();
    }
  }

  openssl_streambuf::int_type openssl_streambuf::underflow()
  {
    int n = m_stream.sslRead(m_buffer, m_bufsize);
    if (n <= 0)
      return traits_type::eof();

    setg(m_buffer, m_buffer, m_buffer + n);
    return (int_type)(unsigned char)m_buffer[0];
  }

  int openssl_streambuf::sync()
  {
    try
    {
      if (pptr() != pbase())
      {
        int n = m_stream.sslWrite(pbase(), pptr() - pbase());
        if (n <= 0)
          return -1;
        else
          setp(m_buffer, m_buffer + m_bufsize);
      }
      return 0;
    }
    catch (const std::exception& e)
    {
      log_error("error in openssl_streambuf::sync(): " << e.what());
      return traits_type::eof();
    }
  }

  //////////////////////////////////////////////////////////////////////
  // openssl_iostream
  //
}
<|endoftext|>


<commit_before>/*
 * Copyright 2017 deepstreamHub GmbH
 *
 * 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 <cstdint>

#include <algorithm>
#include <chrono>
#include <stdexcept>
#include <sstream>

#include "connection.hpp"
#include "message.hpp"
#include "message_builder.hpp"
#include "state.hpp"
#include "use.hpp"
#include <deepstream/core/buffer.hpp>
#include <deepstream/core/client.hpp>
#include <deepstream/core/error_handler.hpp>
#include <deepstream/core/event.hpp>
#include <deepstream/core/presence.hpp>

#include <cassert>

#ifndef NDEBUG
#include <iostream>
#define DEBUG_MSG(str) do { std::cout << "\033[1,31m#\033[0m " << str << std::endl; } while( false )
#else
#define DEBUG_MSG(str) do { } while ( false )
#endif

namespace deepstream {

    Connection::Connection(const std::string &uri, WSHandler &ws_handler,
            ErrorHandler &error_handler, Event &event, Presence &presence)
        : state_(ConnectionState::CLOSED)
        , error_handler_(error_handler)
        , ws_handler_(ws_handler)
        , p_login_callback_(nullptr)
        , p_auth_params_(nullptr)
        , event_(event)
        , presence_(presence)
    {
        assert(ws_handler.state() == WSState::CLOSED);

        ws_handler.URI(uri);

        using namespace std::placeholders;
        ws_handler.on_message(std::bind(&Connection::on_message, this, _1));
        ws_handler.on_error(std::bind(&Connection::on_error, this, _1));
        ws_handler.on_open(std::bind(&Connection::on_open, this));
        ws_handler.on_close(std::bind(&Connection::on_close, this));

        ws_handler.open();
    }

    void Connection::login(const std::string& auth_params, const Client::LoginCallback &callback)
    {
        if (state_ == ConnectionState::OPEN) {
            return;
        }

        p_login_callback_ = std::unique_ptr<Client::LoginCallback>(new Client::LoginCallback(callback));

        p_auth_params_ = std::unique_ptr<std::string>(new std::string(auth_params));

        if (state_ == ConnectionState::AWAIT_AUTHENTICATION) {
            send_authentication_request();
        }
    }

    void Connection::send_authentication_request()
    {
        assert(state_ == ConnectionState::AWAIT_AUTHENTICATION);
        assert(p_auth_params_);

        MessageBuilder authentication_request(Topic::AUTH, Action::REQUEST);
        authentication_request.add_argument(*p_auth_params_);

        send(authentication_request);
    }

    void Connection::close()
    {
        deliberate_close_ = true;
        ws_handler_.close();
    }

    ConnectionState Connection::state()
    {
        return state_;
    }

    void Connection::state(const ConnectionState state)
    {
        if (state != state_) {
            state_ = state;
            on_connection_state_change_();
        }
    }

    void Connection::on_connection_state_change_()
    {
        DEBUG_MSG("Connection state change: " << state_);
        event_.on_connection_state_change_(state_);
        //presence_.on_connection_state_change_(state_);
    }

    void Connection::on_message(const Buffer &&raw_message)
    {
        Buffer buffer(raw_message.size() + 2, 0);
        std::copy(raw_message.cbegin(), raw_message.cend(), buffer.begin());

        auto parser_result = parser::execute(buffer.data(), buffer.size());
        const parser::ErrorList& errors = parser_result.second;

        for (auto it = errors.cbegin(); it != errors.cend(); ++it) {
            const parser::Error &error = *it;
            std::stringstream error_message;
            error_message << "parser error: " << error << " \""
                << Message::to_human_readable(raw_message) << "\"";
            error_handler_.on_error(error_message.str());
        }

        parser::MessageList parsed_messages(std::move(parser_result.first));

        for (auto it = parsed_messages.cbegin(); it != parsed_messages.cend(); ++it) {
            const Message &parsed_message = *it;
            DEBUG_MSG("Message received: " << parsed_message.header());

            switch (parsed_message.topic()) {
                case Topic::EVENT:
                    event_.notify_(parsed_message);
                    break;

                case Topic::PRESENCE:
                    presence_.notify_(parsed_message);
                    break;

                case Topic::CONNECTION:
                    handle_connection_response(parsed_message);
                    break;

                case Topic::AUTH:
                    handle_authentication_response(parsed_message);
                    break;

                default:
                    {
                        std::stringstream error_message;
                        error_message << "unsolicited message: " << parsed_message.header();
                        error_handler_.on_error(error_message.str());
                        assert(0);
                    }
            }

        }
    }

    void Connection::handle_connection_response(const Message &message)
    {
        ConnectionState new_state = transition_incoming(state_, message);

        if (new_state == ConnectionState::ERROR) {
            std::stringstream error_message;
            error_message << "connection error state reached, previous state: " << state_
                << ", message header: " << message.header();
            error_handler_.on_error(error_message.str());
        }

        state(new_state);
        switch(message.action()) {
            case Action::PING:
                {
                    const MessageBuilder pong(Topic::CONNECTION, Action::PONG);
                    send(pong);
                } break;
            case Action::CHALLENGE:
                {
                    MessageBuilder challenge_response(Topic::CONNECTION, Action::CHALLENGE_RESPONSE);
                    challenge_response.add_argument(ws_handler_.URI());
                    send(challenge_response);
                } break;
            case Action::CHALLENGE_RESPONSE:
                {
                    if (p_auth_params_) {
                        send_authentication_request();
                    }
                } break;
            case Action::REDIRECT:
                {
                    if (message.num_arguments() < 1) {
                        error_handler_.on_error("No URI given in connection redirect message");
                        break;
                    }
                    const Buffer &uri_buff(message[0]);
                    const std::string uri(uri_buff.cbegin(), uri_buff.cend());
                    DEBUG_MSG("redirecting to \"" << uri << "\"");
                    ws_handler_.URI(uri);
                } break;
            default:
                {
                    std::stringstream error_message;
                    error_message << "connection message with unknown action " << state_
                        << std::endl << '\t' << message.header();
                    error_handler_.on_error(error_message.str());
                }
        }
    }

    void Connection::handle_authentication_response(const Message &message)
    {
        switch(message.action()) {
            case Action::ERROR_TOO_MANY_AUTH_ATTEMPTS:
                {
                    error_handler_.on_error("too many authentication attempts");
                    close();
                } break;
            case Action::ERROR_INVALID_AUTH_DATA:
                {
                    error_handler_.on_error("invalid auth data");
                    close();
                } break;
            case Action::ERROR_INVALID_AUTH_MSG:
                {
                    error_handler_.on_error("invalid auth message");
                    close();
                } break;
            case Action::REQUEST:
                {
                    assert(message.is_ack());
                    //TODO: flush message buffer
                    if (p_login_callback_) {
                        const auto login_callback = *p_login_callback_;
                        if (message.num_arguments() < 1) {
                            login_callback(std::unique_ptr<Buffer>(nullptr));
                        } else {
                            login_callback(std::unique_ptr<Buffer>(new Buffer(message[0])));
                        }
                    }
                } break;
            default:
                {
                    std::stringstream error_message;
                    error_message << "authentication message with unknown action" << state_
                        << ", message header: " << message.header();
                    error_handler_.on_error(error_message.str());
                }
        }

        ConnectionState new_state = transition_incoming(state_, message);

        if (new_state == ConnectionState::ERROR) {
            std::stringstream error_message;
            error_message << "connection error state reached, previous state: " << state_
                << ", message header: " << message.header();
            error_handler_.on_error(error_message.str());
        }

        state(new_state);
    }

    void Connection::on_error(const std::string &&error)
    {
        DEBUG_MSG("Websocket error: " << error);
        state(ConnectionState::RECONNECTING);
        ws_handler_.reconnect();
    }

    void Connection::on_open()
    {
        state(ConnectionState::AWAIT_CONNECTION);
    }

    void Connection::on_close()
    {
        if (deliberate_close_) {
            state(ConnectionState::CLOSED);
            return;
        }
        state(ConnectionState::RECONNECTING);
        ws_handler_.open();
    }

    bool Connection::send(const Message& message)
    {
        DEBUG_MSG("--> Sending message: " << message.header());

        if (message.topic() == Topic::CONNECTION || message.topic() == Topic::AUTH) {
            ConnectionState new_state = transition_outgoing(state_, message);
            assert(new_state != ConnectionState::ERROR);

            if (new_state == ConnectionState::ERROR) {
                throw std::logic_error("Invalid client state transition");
            }

            state(new_state);
        } else if (state_ != ConnectionState::OPEN) {
            return false;
        }

        return ws_handler_.send(message.to_binary());
    }

    ConnectionState transition_incoming(const ConnectionState state, const Message& message)
    {
        assert(state != ConnectionState::ERROR);
        assert(state != ConnectionState::CLOSED);

        const Topic topic = message.topic();
        const Action action = message.action();
        const bool is_ack = message.is_ack();

        const auto expected_num_args = Message::num_arguments(message.header());
        const std::size_t& num_args = message.num_arguments();

        use(expected_num_args);
        use(num_args);
        assert(num_args >= expected_num_args.first);
        assert(num_args <= expected_num_args.second);

        // deepstream ping/pong (distinct from websocket heartbeats)
        if (topic == Topic::CONNECTION && action == Action::PING) {
            assert(!is_ack);

            return state;
        }

        // actual state transitions
        if (state == ConnectionState::AWAIT_CONNECTION && topic == Topic::CONNECTION
                && action == Action::CHALLENGE) {
            assert(!is_ack);

            return ConnectionState::CHALLENGING;
        }

        if (state == ConnectionState::CHALLENGING_WAIT && topic == Topic::CONNECTION
                && action == Action::CHALLENGE_RESPONSE) {
            assert(is_ack);
            return ConnectionState::AWAIT_AUTHENTICATION;
        }

        if (state == ConnectionState::CHALLENGING_WAIT && topic == Topic::CONNECTION
                && action == Action::REDIRECT) {
            assert(!is_ack);

            return ConnectionState::AWAIT_CONNECTION;
        }

        if (state == ConnectionState::CHALLENGING_WAIT && topic == Topic::CONNECTION
                && action == Action::REJECT) {
            assert(!is_ack);

            return ConnectionState::CLOSED;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::REQUEST) {
            assert(is_ack);
            return ConnectionState::OPEN;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::ERROR_TOO_MANY_AUTH_ATTEMPTS) {
            assert(!is_ack);

            return ConnectionState::CLOSED;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::ERROR_INVALID_AUTH_DATA) {
            assert(!is_ack);

            return ConnectionState::AWAIT_AUTHENTICATION;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::ERROR_INVALID_AUTH_MSG) {
            assert(!is_ack);

            return ConnectionState::CLOSED;
        }

        if (state == ConnectionState::OPEN) {
            return state;
        }

        return ConnectionState::ERROR;
    }

    ConnectionState transition_outgoing(const ConnectionState state, const Message& message)
    {
        assert(state != ConnectionState::ERROR);
        assert(state != ConnectionState::CLOSED);

        const Topic topic = message.topic();
        const Action action = message.action();
        const bool is_ack = message.is_ack();

        const auto expected_num_args = Message::num_arguments(message.header());
        const std::size_t& num_args = message.num_arguments();

        use(expected_num_args);
        use(num_args);
        assert(num_args >= expected_num_args.first);
        assert(num_args <= expected_num_args.second);

        if (topic == Topic::CONNECTION && action == Action::PONG) {
            assert(!is_ack);

            return state;
        }

        if (state == ConnectionState::CHALLENGING && topic == Topic::CONNECTION
                && action == Action::CHALLENGE_RESPONSE) {
            assert(!is_ack);
            return ConnectionState::CHALLENGING_WAIT;
        }

        if (state == ConnectionState::AWAIT_AUTHENTICATION && topic == Topic::AUTH
                && action == Action::REQUEST) {
            assert(!is_ack);
            return ConnectionState::AUTHENTICATING;
        }

        return ConnectionState::ERROR;
    }
}
<commit_msg>fix: remove incorrect TODO comment<commit_after>/*
 * Copyright 2017 deepstreamHub GmbH
 *
 * 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 <cstdint>

#include <algorithm>
#include <chrono>
#include <stdexcept>
#include <sstream>

#include "connection.hpp"
#include "message.hpp"
#include "message_builder.hpp"
#include "state.hpp"
#include "use.hpp"
#include <deepstream/core/buffer.hpp>
#include <deepstream/core/client.hpp>
#include <deepstream/core/error_handler.hpp>
#include <deepstream/core/event.hpp>
#include <deepstream/core/presence.hpp>

#include <cassert>

#ifndef NDEBUG
#include <iostream>
#define DEBUG_MSG(str) do { std::cout << "\033[1,31m#\033[0m " << str << std::endl; } while( false )
#else
#define DEBUG_MSG(str) do { } while ( false )
#endif

namespace deepstream {

    Connection::Connection(const std::string &uri, WSHandler &ws_handler,
            ErrorHandler &error_handler, Event &event, Presence &presence)
        : state_(ConnectionState::CLOSED)
        , error_handler_(error_handler)
        , ws_handler_(ws_handler)
        , p_login_callback_(nullptr)
        , p_auth_params_(nullptr)
        , event_(event)
        , presence_(presence)
    {
        assert(ws_handler.state() == WSState::CLOSED);

        ws_handler.URI(uri);

        using namespace std::placeholders;
        ws_handler.on_message(std::bind(&Connection::on_message, this, _1));
        ws_handler.on_error(std::bind(&Connection::on_error, this, _1));
        ws_handler.on_open(std::bind(&Connection::on_open, this));
        ws_handler.on_close(std::bind(&Connection::on_close, this));

        ws_handler.open();
    }

    void Connection::login(const std::string& auth_params, const Client::LoginCallback &callback)
    {
        if (state_ == ConnectionState::OPEN) {
            return;
        }

        p_login_callback_ = std::unique_ptr<Client::LoginCallback>(new Client::LoginCallback(callback));

        p_auth_params_ = std::unique_ptr<std::string>(new std::string(auth_params));

        if (state_ == ConnectionState::AWAIT_AUTHENTICATION) {
            send_authentication_request();
        }
    }

    void Connection::send_authentication_request()
    {
        assert(state_ == ConnectionState::AWAIT_AUTHENTICATION);
        assert(p_auth_params_);

        MessageBuilder authentication_request(Topic::AUTH, Action::REQUEST);
        authentication_request.add_argument(*p_auth_params_);

        send(authentication_request);
    }

    void Connection::close()
    {
        deliberate_close_ = true;
        ws_handler_.close();
    }

    ConnectionState Connection::state()
    {
        return state_;
    }

    void Connection::state(const ConnectionState state)
    {
        if (state != state_) {
            state_ = state;
            on_connection_state_change_();
        }
    }

    void Connection::on_connection_state_change_()
    {
        DEBUG_MSG("Connection state change: " << state_);
        event_.on_connection_state_change_(state_);
        //presence_.on_connection_state_change_(state_);
    }

    void Connection::on_message(const Buffer &&raw_message)
    {
        Buffer buffer(raw_message.size() + 2, 0);
        std::copy(raw_message.cbegin(), raw_message.cend(), buffer.begin());

        auto parser_result = parser::execute(buffer.data(), buffer.size());
        const parser::ErrorList& errors = parser_result.second;

        for (auto it = errors.cbegin(); it != errors.cend(); ++it) {
            const parser::Error &error = *it;
            std::stringstream error_message;
            error_message << "parser error: " << error << " \""
                << Message::to_human_readable(raw_message) << "\"";
            error_handler_.on_error(error_message.str());
        }

        parser::MessageList parsed_messages(std::move(parser_result.first));

        for (auto it = parsed_messages.cbegin(); it != parsed_messages.cend(); ++it) {
            const Message &parsed_message = *it;
            DEBUG_MSG("Message received: " << parsed_message.header());

            switch (parsed_message.topic()) {
                case Topic::EVENT:
                    event_.notify_(parsed_message);
                    break;

                case Topic::PRESENCE:
                    presence_.notify_(parsed_message);
                    break;

                case Topic::CONNECTION:
                    handle_connection_response(parsed_message);
                    break;

                case Topic::AUTH:
                    handle_authentication_response(parsed_message);
                    break;

                default:
                    {
                        std::stringstream error_message;
                        error_message << "unsolicited message: " << parsed_message.header();
                        error_handler_.on_error(error_message.str());
                        assert(0);
                    }
            }

        }
    }

    void Connection::handle_connection_response(const Message &message)
    {
        ConnectionState new_state = transition_incoming(state_, message);

        if (new_state == ConnectionState::ERROR) {
            std::stringstream error_message;
            error_message << "connection error state reached, previous state: " << state_
                << ", message header: " << message.header();
            error_handler_.on_error(error_message.str());
        }

        state(new_state);
        switch(message.action()) {
            case Action::PING:
                {
                    const MessageBuilder pong(Topic::CONNECTION, Action::PONG);
                    send(pong);
                } break;
            case Action::CHALLENGE:
                {
                    MessageBuilder challenge_response(Topic::CONNECTION, Action::CHALLENGE_RESPONSE);
                    challenge_response.add_argument(ws_handler_.URI());
                    send(challenge_response);
                } break;
            case Action::CHALLENGE_RESPONSE:
                {
                    if (p_auth_params_) {
                        send_authentication_request();
                    }
                } break;
            case Action::REDIRECT:
                {
                    if (message.num_arguments() < 1) {
                        error_handler_.on_error("No URI given in connection redirect message");
                        break;
                    }
                    const Buffer &uri_buff(message[0]);
                    const std::string uri(uri_buff.cbegin(), uri_buff.cend());
                    DEBUG_MSG("redirecting to \"" << uri << "\"");
                    ws_handler_.URI(uri);
                } break;
            default:
                {
                    std::stringstream error_message;
                    error_message << "connection message with unknown action " << state_
                        << std::endl << '\t' << message.header();
                    error_handler_.on_error(error_message.str());
                }
        }
    }

    void Connection::handle_authentication_response(const Message &message)
    {
        switch(message.action()) {
            case Action::ERROR_TOO_MANY_AUTH_ATTEMPTS:
                {
                    error_handler_.on_error("too many authentication attempts");
                    close();
                } break;
            case Action::ERROR_INVALID_AUTH_DATA:
                {
                    error_handler_.on_error("invalid auth data");
                    close();
                } break;
            case Action::ERROR_INVALID_AUTH_MSG:
                {
                    error_handler_.on_error("invalid auth message");
                    close();
                } break;
            case Action::REQUEST:
                {
                    assert(message.is_ack());
                    if (p_login_callback_) {
                        const auto login_callback = *p_login_callback_;
                        if (message.num_arguments() < 1) {
                            login_callback(std::unique_ptr<Buffer>(nullptr));
                        } else {
                            login_callback(std::unique_ptr<Buffer>(new Buffer(message[0])));
                        }
                    }
                } break;
            default:
                {
                    std::stringstream error_message;
                    error_message << "authentication message with unknown action" << state_
                        << ", message header: " << message.header();
                    error_handler_.on_error(error_message.str());
                }
        }

        ConnectionState new_state = transition_incoming(state_, message);

        if (new_state == ConnectionState::ERROR) {
            std::stringstream error_message;
            error_message << "connection error state reached, previous state: " << state_
                << ", message header: " << message.header();
            error_handler_.on_error(error_message.str());
        }

        state(new_state);
    }

    void Connection::on_error(const std::string &&error)
    {
        DEBUG_MSG("Websocket error: " << error);
        state(ConnectionState::RECONNECTING);
        ws_handler_.reconnect();
    }

    void Connection::on_open()
    {
        state(ConnectionState::AWAIT_CONNECTION);
    }

    void Connection::on_close()
    {
        if (deliberate_close_) {
            state(ConnectionState::CLOSED);
            return;
        }
        state(ConnectionState::RECONNECTING);
        ws_handler_.open();
    }

    bool Connection::send(const Message& message)
    {
        DEBUG_MSG("--> Sending message: " << message.header());

        if (message.topic() == Topic::CONNECTION || message.topic() == Topic::AUTH) {
            ConnectionState new_state = transition_outgoing(state_, message);
            assert(new_state != ConnectionState::ERROR);

            if (new_state == ConnectionState::ERROR) {
                throw std::logic_error("Invalid client state transition");
            }

            state(new_state);
        } else if (state_ != ConnectionState::OPEN) {
            return false;
        }

        return ws_handler_.send(message.to_binary());
    }

    ConnectionState transition_incoming(const ConnectionState state, const Message& message)
    {
        assert(state != ConnectionState::ERROR);
        assert(state != ConnectionState::CLOSED);

        const Topic topic = message.topic();
        const Action action = message.action();
        const bool is_ack = message.is_ack();

        const auto expected_num_args = Message::num_arguments(message.header());
        const std::size_t& num_args = message.num_arguments();

        use(expected_num_args);
        use(num_args);
        assert(num_args >= expected_num_args.first);
        assert(num_args <= expected_num_args.second);

        // deepstream ping/pong (distinct from websocket heartbeats)
        if (topic == Topic::CONNECTION && action == Action::PING) {
            assert(!is_ack);

            return state;
        }

        // actual state transitions
        if (state == ConnectionState::AWAIT_CONNECTION && topic == Topic::CONNECTION
                && action == Action::CHALLENGE) {
            assert(!is_ack);

            return ConnectionState::CHALLENGING;
        }

        if (state == ConnectionState::CHALLENGING_WAIT && topic == Topic::CONNECTION
                && action == Action::CHALLENGE_RESPONSE) {
            assert(is_ack);
            return ConnectionState::AWAIT_AUTHENTICATION;
        }

        if (state == ConnectionState::CHALLENGING_WAIT && topic == Topic::CONNECTION
                && action == Action::REDIRECT) {
            assert(!is_ack);

            return ConnectionState::AWAIT_CONNECTION;
        }

        if (state == ConnectionState::CHALLENGING_WAIT && topic == Topic::CONNECTION
                && action == Action::REJECT) {
            assert(!is_ack);

            return ConnectionState::CLOSED;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::REQUEST) {
            assert(is_ack);
            return ConnectionState::OPEN;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::ERROR_TOO_MANY_AUTH_ATTEMPTS) {
            assert(!is_ack);

            return ConnectionState::CLOSED;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::ERROR_INVALID_AUTH_DATA) {
            assert(!is_ack);

            return ConnectionState::AWAIT_AUTHENTICATION;
        }

        if (state == ConnectionState::AUTHENTICATING && topic == Topic::AUTH
                && action == Action::ERROR_INVALID_AUTH_MSG) {
            assert(!is_ack);

            return ConnectionState::CLOSED;
        }

        if (state == ConnectionState::OPEN) {
            return state;
        }

        return ConnectionState::ERROR;
    }

    ConnectionState transition_outgoing(const ConnectionState state, const Message& message)
    {
        assert(state != ConnectionState::ERROR);
        assert(state != ConnectionState::CLOSED);

        const Topic topic = message.topic();
        const Action action = message.action();
        const bool is_ack = message.is_ack();

        const auto expected_num_args = Message::num_arguments(message.header());
        const std::size_t& num_args = message.num_arguments();

        use(expected_num_args);
        use(num_args);
        assert(num_args >= expected_num_args.first);
        assert(num_args <= expected_num_args.second);

        if (topic == Topic::CONNECTION && action == Action::PONG) {
            assert(!is_ack);

            return state;
        }

        if (state == ConnectionState::CHALLENGING && topic == Topic::CONNECTION
                && action == Action::CHALLENGE_RESPONSE) {
            assert(!is_ack);
            return ConnectionState::CHALLENGING_WAIT;
        }

        if (state == ConnectionState::AWAIT_AUTHENTICATION && topic == Topic::AUTH
                && action == Action::REQUEST) {
            assert(!is_ack);
            return ConnectionState::AUTHENTICATING;
        }

        return ConnectionState::ERROR;
    }
}
<|endoftext|>


<commit_before>#include <smoke.h>
#ifdef HAVE_QTGUI
#include <smoke/qtgui_smoke.h>
#endif

#include <algorithm>
#include <vector>
#include <stdlib.h>
#include <stdio.h>

extern "C" {
  Smoke** smokeInitialize()
  {
    std::vector<Smoke*> smokes;

#ifdef HAVE_QTGUI
    init_qtgui_Smoke();
    smokes.push_back(qtgui_Smoke);
    printf("Initialized qtgui\n");
#endif
    smokes.push_back(0);
    printf("%d smokes (w/ null terminator)\n", smokes.size());

    Smoke **ret = (Smoke**)calloc(smokes.size(), sizeof(Smoke*));
    std::copy(smokes.begin(), smokes.end(), ret);

    return ret;
  }

  Smoke::Class* smokeClasses(Smoke *smoke)
  {
    return smoke->classes;
  }

  int smokeNumClasses(Smoke *smoke)
  {
    return smoke->numClasses;
  }

  Smoke::Method* smokeMethods(Smoke *smoke)
  {
    return smoke->methods;
  }

  int smokeNumMethods(Smoke *smoke)
  {
    return smoke->numMethods;
  }

  const char** smokeMethodNames(Smoke *smoke)
  {
    return smoke->methodNames;
  }

  const int smokeNumMethodNames(Smoke *smoke)
  {
    return smoke->numMethodNames;
  }

  Smoke::Type* smokeTypes(Smoke *smoke)
  {
    return smoke->types;
  }

  int smokeNumTypes(Smoke *smoke)
  {
    return smoke->numTypes;
  }

  short* smokeArgumentList(Smoke *smoke)
  {
    return smoke->argumentList;
  }

  const char* smokeModuleName(Smoke *smoke)
  {
    return smoke->moduleName();
  }

  const char* smokeClassName(Smoke::Class *klass)
  {
    return klass->className;
  }

  int smokeClassExternal(Smoke::Class *klass)
  {
    return klass->external;
  }

  int smokeClassParents(Smoke::Class *klass)
  {
    return klass->parents;
  }

  unsigned int smokeClassFlags(Smoke::Class *klass)
  {
    return klass->flags;
  }

  int smokeClassSize(Smoke::Class *klass)
  {
    return klass->size;
  }

  int smokeMethodClassId(Smoke::Method *m)
  {
    return m->classId;
  }

  int smokeMethodName(Smoke::Method *m)
  {
    return m->name;
  }

  int smokeMethodArgs(Smoke::Method *m)
  {
    return m->args;
  }

  int smokeMethodNumArgs(Smoke::Method *m)
  {
    return m->numArgs;
  }

  unsigned int smokeMethodFlags(Smoke::Method *m)
  {
    return m->flags;
  }

  int smokeMethodRet(Smoke::Method *m)
  {
    return m->ret;
  }

  int smokeMethodMethod(Smoke::Method *m)
  {
    return m->method;
  }

  const char* smokeTypeName(Smoke::Type *t)
  {
    return t->name;
  }

  int smokeTypeClassId(Smoke::Type *t)
  {
    return t->classId;
  }

  unsigned int smokeTypeFlags(Smoke::Type *t)
  {
    return t->flags;
  }
}

#if defined(PROBE)
template<typename T> struct align { char c; T member; };
#define ALIGNOF(ty) offsetof(align<ty>, member)

#include <stdio.h>

int main() {
  printf("Class align=%d and size=%d\n",
      ALIGNOF(Smoke::Class), sizeof(Smoke::Class));
  printf("Method align=%d and size=%d\n",
      ALIGNOF(Smoke::Method), sizeof(Smoke::Method));
  printf("Type align=%d and size=%d\n",
      ALIGNOF(Smoke::Type), sizeof(Smoke::Type));

  return 0;
}
#endif
<commit_msg>Remove printfs<commit_after>#include <smoke.h>
#ifdef HAVE_QTGUI
#include <smoke/qtgui_smoke.h>
#endif

#include <algorithm>
#include <vector>
#include <stdlib.h>

extern "C" {
  Smoke** smokeInitialize()
  {
    std::vector<Smoke*> smokes;

#ifdef HAVE_QTGUI
    init_qtgui_Smoke();
    smokes.push_back(qtgui_Smoke);
#endif
    smokes.push_back(0);

    Smoke **ret = (Smoke**)calloc(smokes.size(), sizeof(Smoke*));
    std::copy(smokes.begin(), smokes.end(), ret);

    return ret;
  }

  Smoke::Class* smokeClasses(Smoke *smoke)
  {
    return smoke->classes;
  }

  int smokeNumClasses(Smoke *smoke)
  {
    return smoke->numClasses;
  }

  Smoke::Method* smokeMethods(Smoke *smoke)
  {
    return smoke->methods;
  }

  int smokeNumMethods(Smoke *smoke)
  {
    return smoke->numMethods;
  }

  const char** smokeMethodNames(Smoke *smoke)
  {
    return smoke->methodNames;
  }

  const int smokeNumMethodNames(Smoke *smoke)
  {
    return smoke->numMethodNames;
  }

  Smoke::Type* smokeTypes(Smoke *smoke)
  {
    return smoke->types;
  }

  int smokeNumTypes(Smoke *smoke)
  {
    return smoke->numTypes;
  }

  short* smokeArgumentList(Smoke *smoke)
  {
    return smoke->argumentList;
  }

  const char* smokeModuleName(Smoke *smoke)
  {
    return smoke->moduleName();
  }

  const char* smokeClassName(Smoke::Class *klass)
  {
    return klass->className;
  }

  int smokeClassExternal(Smoke::Class *klass)
  {
    return klass->external;
  }

  int smokeClassParents(Smoke::Class *klass)
  {
    return klass->parents;
  }

  unsigned int smokeClassFlags(Smoke::Class *klass)
  {
    return klass->flags;
  }

  int smokeClassSize(Smoke::Class *klass)
  {
    return klass->size;
  }

  int smokeMethodClassId(Smoke::Method *m)
  {
    return m->classId;
  }

  int smokeMethodName(Smoke::Method *m)
  {
    return m->name;
  }

  int smokeMethodArgs(Smoke::Method *m)
  {
    return m->args;
  }

  int smokeMethodNumArgs(Smoke::Method *m)
  {
    return m->numArgs;
  }

  unsigned int smokeMethodFlags(Smoke::Method *m)
  {
    return m->flags;
  }

  int smokeMethodRet(Smoke::Method *m)
  {
    return m->ret;
  }

  int smokeMethodMethod(Smoke::Method *m)
  {
    return m->method;
  }

  const char* smokeTypeName(Smoke::Type *t)
  {
    return t->name;
  }

  int smokeTypeClassId(Smoke::Type *t)
  {
    return t->classId;
  }

  unsigned int smokeTypeFlags(Smoke::Type *t)
  {
    return t->flags;
  }
}

#if defined(PROBE)
template<typename T> struct align { char c; T member; };
#define ALIGNOF(ty) offsetof(align<ty>, member)

#include <stdio.h>

int main() {
  printf("Class align=%d and size=%d\n",
      ALIGNOF(Smoke::Class), sizeof(Smoke::Class));
  printf("Method align=%d and size=%d\n",
      ALIGNOF(Smoke::Method), sizeof(Smoke::Method));
  printf("Type align=%d and size=%d\n",
      ALIGNOF(Smoke::Type), sizeof(Smoke::Type));

  return 0;
}
#endif
<|endoftext|>