project(json_gssapi)
-add_executable(json_gssapi src/datamodel/GSSName.cpp src/datamodel/GSSOID.cpp src/datamodel/GSSBuffer.cpp src/GSSException.cpp
+add_executable(json_gssapi src/datamodel/GSSName.cpp
+ src/datamodel/GSSOID.cpp
+ src/datamodel/GSSOIDSet.cpp
+ src/datamodel/GSSBuffer.cpp
+ src/datamodel/GSSCredential.cpp
+ src/GSSException.cpp
src/GSSCreateSecContextCommand.cpp
src/GSSCommand.cpp
src/GSSImportName.cpp
+ src/GSSAcquireCred.cpp
src/util_json.cpp
main.cpp)
target_link_libraries(json_gssapi gssapi_krb5 jansson)
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "GSSAcquireCred.h"
+#include "GSSException.h"
+
+GSSAcquireCred::GSSAcquireCred(gss_acq_cred_type fn) : function(fn)
+{
+
+}
+
+GSSAcquireCred::GSSAcquireCred ( const GSSAcquireCred& other )
+{
+
+}
+
+GSSAcquireCred::~GSSAcquireCred()
+{
+
+}
+
+GSSAcquireCred& GSSAcquireCred::operator= ( const GSSAcquireCred& other )
+{
+ return(*this);
+}
+
+GSSAcquireCred::GSSAcquireCred ( JSONObject params ) : GSSCommand ( params )
+{
+
+}
+
+
+void GSSAcquireCred::execute()
+{
+ /* Variables */
+ gss_cred_id_t output_cred_handle;
+ gss_OID_set actual_mechs;
+
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ this->retVal = function(
+ &this->minor_status,
+ this->desired_name.toGss(),
+ this->time_req,
+ this->desiredMechs.toGss(),
+ this->cred_usage,
+ &output_cred_handle,
+ &actual_mechs,
+ &this->time_rec
+ );
+
+ if (GSS_ERROR(this->retVal) )
+ {
+ std::string err("Error acquiring credential for user '");
+ err += desired_name.toString();
+ err += "'.";
+ throw GSSException(err, this->retVal, this->minor_status);
+ }
+
+ this->cred.setValue(output_cred_handle);
+ this->actualMechs = actual_mechs;
+
+ /* Cleanup */
+ /* Return */
+}
+
+JSONObject *GSSAcquireCred::toJSON()
+{
+ return new JSONObject();
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#ifndef GSSACQUIRECREDCOMMAND_H
+#define GSSACQUIRECREDCOMMAND_H
+
+#include "GSSCommand.h"
+#include "datamodel/GSSCredential.h"
+#include "datamodel/GSSOIDSet.h"
+#include "datamodel/GSSName.h"
+#include <gssapi/gssapi.h>
+
+class GSSAcquireCred : GSSCommand
+{
+public:
+ GSSAcquireCred( gss_acq_cred_type fn = &gss_acquire_cred );
+ GSSAcquireCred ( const GSSAcquireCred& other );
+ GSSAcquireCred ( JSONObject params );
+ ~GSSAcquireCred();
+
+ GSSAcquireCred& operator= ( const GSSAcquireCred& other );
+
+ virtual void execute();
+ virtual JSONObject* toJSON();
+
+ gss_acq_cred_type getGSSFunction() { return function; };
+
+ void addDesiredMech ( const GSSOID &newOID ) { this->desiredMechs.addOID(newOID.toGss()); }
+
+ // Getters & Setters
+ void setDesiredName(const GSSName &name) { this->desired_name = name; }
+ void setTimeReq(OM_uint32 request) { this->time_req = request; }
+ void setCredUsage ( OM_uint32 credential_usage ) {this->cred_usage = credential_usage; }
+
+ GSSName getDesiredName() { return(desired_name); }
+ OM_uint32 getTimeReq() { return(time_req); }
+ gss_cred_id_t getOutputCredHandle() const { return( cred.toGss() ); }
+ OM_uint32 getRetVal() { return(retVal); }
+ OM_uint32 getMinorStatus() { return(minor_status); }
+ gss_OID_set getDesiredMechs() const { return(desiredMechs.toGss()); }
+ gss_cred_usage_t getCredUsage() const { return(cred_usage); }
+ gss_OID_set getActualMechs() const { return( actualMechs.toGss() ); }
+ OM_uint32 getTimeRec() { return(time_rec); }
+
+private:
+ GSSName desired_name;
+ GSSCredential cred;
+ OM_uint32 time_req, time_rec;
+ GSSOIDSet desiredMechs;
+ GSSOIDSet actualMechs;
+ gss_cred_usage_t cred_usage;
+ OM_uint32 retVal, minor_status;
+
+ gss_acq_cred_type function;
+
+};
+
+#endif // GSSACQUIRECREDCOMMAND_H
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "GSSCredential.h"
+#include "../GSSException.h"
+
+GSSCredential::GSSCredential()
+{
+ this->credential = GSS_C_NO_CREDENTIAL;
+}
+
+GSSCredential::GSSCredential ( const GSSCredential& other )
+{
+ this->credential = other.credential;
+}
+
+GSSCredential& GSSCredential::operator= ( const GSSCredential& gsscred )
+{
+ this->credential = gsscred.credential;
+ return(*this);
+}
+
+GSSCredential::~GSSCredential()
+{
+ /* Variables */
+ OM_uint32 major, minor;
+
+ /* Error checking */
+ if (this->credential == GSS_C_NO_CREDENTIAL)
+ return;
+
+ /* Setup */
+
+ /* Main */
+ major = gss_release_cred(&minor, &(this->credential));
+
+ /* Cleanup */
+ if (GSS_ERROR(major))
+ throw GSSException("Could not free the GSS credential", major, minor);
+
+ /* Return */
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#ifndef GSSCREDENTIAL_H
+#define GSSCREDENTIAL_H
+
+#include <gssapi/gssapi.h>
+
+typedef OM_uint32 (*gss_acq_cred_type)(
+ OM_uint32 *, /* minor_status */
+ gss_name_t, /* desired_name */
+ OM_uint32, /* time_req */
+ gss_OID_set, /* desired_mechs */
+ gss_cred_usage_t, /* cred_usage */
+ gss_cred_id_t *, /* output_cred_handle */
+ gss_OID_set *, /* actual_mechs */
+ OM_uint32 *); /* time_rec */
+
+
+
+class GSSCredential
+{
+public:
+ GSSCredential();
+ GSSCredential(const GSSCredential &other);
+ GSSCredential(gss_cred_id_t cred) : credential(cred) {};
+ ~GSSCredential();
+
+ GSSCredential& operator= (const GSSCredential &gsscred);
+
+ gss_cred_id_t toGss() const { return(credential); }
+
+ void setValue(gss_cred_id_t cred) { this->credential = cred; }
+
+private:
+ gss_cred_id_t credential;
+
+};
+
+#endif // GSSCREDENTIAL_H
}
-std::string GSSName::toString()
+std::string GSSName::toString() const
{
/* Variables */
OM_uint32 major, minor;
~GSSName();
- gss_name_t toGSS() { return(name); }
- std::string toString();
+ gss_name_t toGss() const { return(name); }
+ std::string toString() const;
bool setValue(gss_name_t newName);
- bool setKey(std::string key) { this->hashKey = key; }
+ void setKey(std::string key) { this->hashKey = key; }
std::string getKey() const { return this->hashKey; }
OM_uint32 getMajorStatus() const { return this->major_status; }
GSSOID(const GSSOID &gssoid);
~GSSOID();
- gss_OID toGss() { return(oid); };
+ gss_OID toGss() const { return(oid); };
std::string toString();
bool setValue(GSSBuffer buf);
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "GSSOIDSet.h"
+#include "../GSSException.h"
+
+#include <cstring>
+
+GSSOIDSet::GSSOIDSet( )
+{
+ init();
+}
+
+void GSSOIDSet::init()
+{
+ /* Variables */
+ OM_uint32 major, minor;
+
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ major = gss_create_empty_oid_set(&minor, &(this->set));
+
+ // How the heck would this happen, anyway?
+ if (GSS_ERROR(major))
+ throw GSSException("Could not create an empty OID set.", major, minor);
+
+ /* Cleanup */
+ /* return */
+}
+
+GSSOIDSet::~GSSOIDSet()
+{
+ this->releaseOIDSet();
+}
+
+void GSSOIDSet::releaseOIDSet()
+{
+ /* Variables */
+ OM_uint32 major, minor;
+
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ major = gss_release_oid_set(&minor, &(this->set));
+ if (GSS_ERROR(major))
+ throw GSSException("Error in releasing memory for an OID set", major, minor);
+ /* Cleanup */
+ /* return */
+}
+
+GSSOIDSet& GSSOIDSet::operator= ( const gss_OID_set other )
+{
+ /* Variables */
+ OM_uint32 i;
+ gss_OID element;
+
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ this->releaseOIDSet();
+ this->init();
+ for(i = 0; i < other->count; i++)
+ {
+ element = other->elements + i;
+ this->addOID(element);
+ }
+
+ /* Cleanup */
+ /* return */
+ return(*this);
+}
+
+void GSSOIDSet::addOID ( const GSSOID other )
+{
+ this->addOID(other.toGss());
+}
+
+void GSSOIDSet::addOID( const gss_OID other )
+{
+ /* Variables */
+ OM_uint32 major, minor;
+
+ /* Error checking */
+ /* Setup */
+ major = gss_add_oid_set_member(&minor, other, &(this->set) );
+ if (GSS_ERROR(major))
+ throw GSSException("Error encountered while trying to add another OID to a set", major, minor);
+
+ /* Cleanup */
+ /* return */
+}
+
+
+
+ /* Variables */
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ /* Cleanup */
+ /* return */
+
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#ifndef GSSOIDSET_H
+#define GSSOIDSET_H
+#include "GSSOID.h"
+
+class GSSOIDSet
+{
+public:
+ GSSOIDSet();
+ ~GSSOIDSet();
+
+ GSSOIDSet& operator= ( const gss_OID_set other );
+ void addOID (const GSSOID other );
+ void addOID (const gss_OID other );
+ gss_OID_set toGss() const { return(set); }
+
+private:
+ gss_OID_set set;
+
+ GSSOIDSet ( const GSSOIDSet& other ) {};
+ void init();
+ gss_OID copyOID(gss_OID source, gss_OID destination);
+ void releaseOIDSet();
+};
+
+#endif // GSSOIDSET_H
include_directories(${CMAKE_SOURCE_DIR}/src)
add_executable(test GSSExceptionTest.cpp
- InitSecContextMock.cpp
+ GSSAcquireCredTest.cpp
+ command_mocks/InitSecContextMock.cpp
+ command_mocks/MockAcquireCred.cpp
GSSCreateSecContextTest.cpp
GSSImportNameTest.cpp
- MockImportName.cpp
+ command_mocks/MockImportName.cpp
test_run.cpp
../src/GSSCreateSecContextCommand.cpp
../src/util_json.cpp
../src/GSSImportName.cpp
../src/GSSException.cpp
+ ../src/GSSAcquireCred.cpp
../src/datamodel/GSSBuffer.cpp
+ ../src/datamodel/GSSCredential.cpp
../src/datamodel/GSSName.cpp
../src/datamodel/GSSOID.cpp
+ ../src/datamodel/GSSOIDSet.cpp
datamodel/GSSBufferTest.cpp
+ datamodel/GSSOIDSetTest.cpp
)
target_link_libraries(test cppunit gssapi_krb5 jansson)
# install(TARGETS test RUNTIME DESTINATION bin)
-add_subdirectory(datamodel)
\ No newline at end of file
+add_subdirectory(datamodel)
+add_subdirectory(command_mocks)
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "GSSAcquireCredTest.h"
+#include "GSSAcquireCred.h"
+#include "command_mocks/MockAcquireCred.h"
+#include <datamodel/GSSName.h>
+#include <gssapi.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION( GSSAcquireCredTest );
+
+OM_uint32 KRB5_CALLCONV
+mock_acquire_cred(
+ OM_uint32 *minor_status,
+ gss_name_t desired_name,
+ OM_uint32 time_req,
+ gss_OID_set desired_mechs,
+ gss_cred_usage_t cred_usage,
+ gss_cred_id_t * output_cred_handle,
+ gss_OID_set * actual_mechs,
+ OM_uint32 *time_rec)
+{
+
+ // Set MockAcquireCred attributes from our in-parameters
+ MockAcquireCred::desired_name = desired_name;
+ MockAcquireCred::time_req = time_req;
+ MockAcquireCred::desired_mechs = desired_mechs;
+ MockAcquireCred::cred_usage = cred_usage;
+
+ // Set our out-parameters from MockAcquireCred
+ *output_cred_handle = MockAcquireCred::output_cred_handle;
+ *actual_mechs = MockAcquireCred::actual_mechs;
+ *time_rec = MockAcquireCred::time_rec;
+
+ *minor_status = MockAcquireCred::minor_status;
+ return(MockAcquireCred::retVal);
+}
+
+void GSSAcquireCredTest::setUp()
+{
+
+}
+
+void GSSAcquireCredTest::tearDown()
+{
+
+}
+
+void GSSAcquireCredTest::testConstructor()
+{
+ /* Variables */
+ GSSAcquireCred cmd = GSSAcquireCred();
+
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The GSSImportName object has the wrong GSS function",
+ &gss_acquire_cred,
+ cmd.getGSSFunction()
+ );
+
+ /* Cleanup */
+ /* Return */
+}
+
+void GSSAcquireCredTest::testConstructorWithJSONObject()
+{
+
+}
+
+void GSSAcquireCredTest::testEmptyCall()
+{
+ /* Variables */
+ GSSAcquireCred cmd = GSSAcquireCred(&mock_acquire_cred);
+ OM_uint32 minor;
+ GSSName steve((char *)"steve@local", (gss_OID)GSS_C_NT_USER_NAME);
+ GSSOID moonshotOID((char *)"{1 3 6 1 5 5 15 1 1 18}");
+
+ /* Error checking */
+ /* Setup */
+ cmd.setDesiredName(steve);
+ cmd.setTimeReq(0);
+ cmd.addDesiredMech( moonshotOID );
+ cmd.setCredUsage(GSS_C_INITIATE);
+
+
+ MockAcquireCred::retVal = 0;
+ MockAcquireCred::minor_status = 0;
+ MockAcquireCred::output_cred_handle = GSS_C_NO_CREDENTIAL;
+ gss_create_empty_oid_set(&minor, &(MockAcquireCred::actual_mechs));
+ MockAcquireCred::time_rec = rand();
+
+ cmd.execute();
+
+ /* Main */
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The desired name was not passed in correctly",
+ steve.toGss(),
+ MockAcquireCred::desired_name
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The time_req was not passed in correctly",
+ cmd.getTimeReq(),
+ MockAcquireCred::time_req
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The desired mechs were not passed in correctly",
+ cmd.getDesiredMechs(),
+ MockAcquireCred::desired_mechs
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The cred_usage was not passed in correctly",
+ cmd.getCredUsage(),
+ MockAcquireCred::cred_usage
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The output credential handle was not passed in correctly",
+ MockAcquireCred::output_cred_handle,
+ cmd.getOutputCredHandle()
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The actual mechanisms value was not passed in correctly",
+ MockAcquireCred::actual_mechs->count,
+ cmd.getActualMechs()->count
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "The time_rec value was not passed in correctly",
+ MockAcquireCred::time_rec,
+ cmd.getTimeRec()
+ );
+
+
+ /* Cleanup */
+ /* Return */
+}
+
+void GSSAcquireCredTest::testJSONMarshal()
+{
+
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#ifndef GSSACQUIRECREDTEST_H
+#define GSSACQUIRECREDTEST_H
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class GSSAcquireCredTest : public CppUnit::TestFixture
+{
+ CPPUNIT_TEST_SUITE( GSSAcquireCredTest );
+ CPPUNIT_TEST( testConstructor );
+ CPPUNIT_TEST( testConstructorWithJSONObject );
+ CPPUNIT_TEST( testEmptyCall );
+ CPPUNIT_TEST( testJSONMarshal );
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructor();
+ void testConstructorWithJSONObject();
+ void testEmptyCall();
+ void testJSONMarshal();
+
+};
+
+#endif // GSSACQUIRECREDTEST_H
#include "GSSCreateSecContextTest.h"
#include "GSSCreateSecContextCommand.h"
-#include "InitSecContextMock.h"
+#include "command_mocks/InitSecContextMock.h"
#include <iostream>
#include <string.h>
#include <exception>
#include "GSSImportNameTest.h"
#include "GSSImportName.h"
-#include "MockImportName.h"
+#include "command_mocks/MockImportName.h"
CPPUNIT_TEST_SUITE_REGISTRATION( GSSImportNameTest );
// copy our output to the appropriate parameters
*minor_status = MockImportName::minor_status;
- *output_name = MockImportName::output_name.toGSS();
+ *output_name = MockImportName::output_name.toGss();
/* Cleanup */
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "MockAcquireCred.h"
+
+/*
+ * class MockAcquireCred
+{
+public:
+ static OM_uint32 retVal;
+ static OM_uint32 minor_status;
+ static gss_name_t desired_name;
+ static OM_uint32 time_req;
+ static gss_OID_set desired_mechs;
+ static gss_cred_usage_t cred_usage;
+ static cred_id_t *output_cred_handle;
+ static gss_OID_set actual_mechs;
+ static OM_uint32 *time_rec;
+
+ static void reset();
+};
+*/
+
+OM_uint32 MockAcquireCred::retVal;
+OM_uint32 MockAcquireCred::minor_status;
+gss_name_t MockAcquireCred::desired_name;
+OM_uint32 MockAcquireCred::time_req;
+gss_OID_set MockAcquireCred::desired_mechs;
+gss_cred_usage_t MockAcquireCred::cred_usage;
+gss_cred_id_t MockAcquireCred::output_cred_handle;
+gss_OID_set MockAcquireCred::actual_mechs;
+OM_uint32 MockAcquireCred::time_rec;
+
+void MockAcquireCred::reset()
+{
+ retVal = 0;
+ minor_status = 0;
+ desired_name = GSS_C_NO_NAME;
+ time_req = 0;
+ desired_mechs = GSS_C_NO_OID_SET;
+ cred_usage = 0;
+ output_cred_handle = GSS_C_NO_CREDENTIAL;
+ actual_mechs = GSS_C_NO_OID_SET;
+ time_rec = 0;
+}
+
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#ifndef MOCKACQUIRECRED_H
+#define MOCKACQUIRECRED_H
+
+#include <gssapi.h>
+
+class MockAcquireCred
+{
+public:
+ static OM_uint32 retVal;
+ static OM_uint32 minor_status;
+ static gss_name_t desired_name;
+ static OM_uint32 time_req;
+ static gss_OID_set desired_mechs;
+ static gss_cred_usage_t cred_usage;
+ static gss_cred_id_t output_cred_handle;
+ static gss_OID_set actual_mechs;
+ static OM_uint32 time_rec;
+
+ static void reset();
+};
+
+#endif // MOCKACQUIRECRED_H
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#include "GSSOIDSetTest.h"
+#include <datamodel/GSSOIDSet.h>
+
+CPPUNIT_TEST_SUITE_REGISTRATION( GSSOIDSetTest );
+
+void GSSOIDSetTest::setUp()
+{
+
+}
+
+void GSSOIDSetTest::tearDown()
+{
+
+}
+
+void GSSOIDSetTest::testConstructor()
+{
+ /* Variables */
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ CPPUNIT_ASSERT_NO_THROW_MESSAGE(
+ "GSSOIDSet constructor without exceptions;",
+ GSSOIDSet subject()
+ );
+
+ /* Cleanup */
+ /* return */
+
+}
+
+void GSSOIDSetTest::testAddOID()
+{
+ /* Variables */
+ GSSOIDSet subject;
+ GSSOID newOID((char *)"{1 3 6 1 5 5 2}");
+
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ CPPUNIT_ASSERT_NO_THROW_MESSAGE(
+ "Adding an OID to the set without an exception",
+ subject.addOID(newOID)
+ );
+
+ CPPUNIT_ASSERT_EQUAL_MESSAGE(
+ "Counter is not incremented after appending an OID to a set",
+ 1,
+ (int)(subject.toGss()->count)
+ );
+
+ /* Cleanup */
+ /* return */
+}
+
+
+ /* Variables */
+ /* Error checking */
+ /* Setup */
+ /* Main */
+ /* Cleanup */
+ /* return */
+
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (c) 2014 <copyright holder> <email>
+ *
+ * For license details, see the LICENSE file in the root of this project.
+ *
+ */
+
+#ifndef GSSOIDSETTEST_H
+#define GSSOIDSETTEST_H
+
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+class GSSOIDSetTest : public CppUnit::TestFixture
+{
+
+ CPPUNIT_TEST_SUITE( GSSOIDSetTest );
+ CPPUNIT_TEST( testConstructor );
+ CPPUNIT_TEST( testAddOID );
+ CPPUNIT_TEST_SUITE_END();
+
+public:
+ virtual void setUp();
+ virtual void tearDown();
+
+ void testConstructor();
+ void testAddOID();
+};
+
+#endif // GSSOIDSETTEST_H
--- /dev/null
+Subproject commit 6f5ecf58ea38c883da6532dc3fb5c1eb4fa9baff