Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EBP-18: As a developer I can submit a cache request and terminate without issue #80

Open
wants to merge 45 commits into
base: SOL-62456
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
efa933e
EBP-19: Applied gofmt to files edited as a part of EBP-19.
TrentDaniel Nov 13, 2024
5012686
EBP-18: Making interim commit so I can pull from SOL-62456.
TrentDaniel Dec 4, 2024
20c9872
EBP-18: Merged branch SOL-62456 into branch EBP-18
TrentDaniel Dec 4, 2024
9c8b66d
EBP-18: Deleted old poc file.
TrentDaniel Dec 12, 2024
30b139a
EBP-18: Making interim commit so that I can pull changes from the fea…
TrentDaniel Dec 12, 2024
9f1b30d
EBP-18: Merging SOL-62456 into EBP-18 to get new feature changes.
TrentDaniel Dec 12, 2024
4384d8d
EBP-18: This commit contains a snapshot of the source that was able to
TrentDaniel Dec 18, 2024
f8271e8
Merge branch 'SOL-62456' into EBP-18
TrentDaniel Dec 19, 2024
7285966
EBP-18: Added testing. Cleanup up source.
TrentDaniel Jan 9, 2025
65e9ded
Merge branch 'SOL-62456' into EBP-18
TrentDaniel Jan 9, 2025
8c60bea
Merge branch 'SOL-62456' into EBP-18
TrentDaniel Jan 9, 2025
d9ef9d4
EBP-18: Made changes in response to PR feedback.
TrentDaniel Jan 13, 2025
aa2abc5
EBP-18: Updated CCSMP version to 100.0.0.993 in response to workaroun…
TrentDaniel Jan 15, 2025
4ef9b68
EBP-18: Implemented refactor of cache functionality as requested in P…
TrentDaniel Jan 20, 2025
3b30663
EBP-18: Fixed linter errors.
TrentDaniel Jan 20, 2025
fb2335e
EBP-18: Updated copyrights and made other changes in response to PR f…
TrentDaniel Jan 20, 2025
7b34813
EBP-18: Fixed SEMP logic issue that was preventing the tests from con…
TrentDaniel Jan 21, 2025
de1909e
EBP-18: Further refactoring in response to PR feedback.
TrentDaniel Jan 22, 2025
6f8f446
EBP-18: Made changes in response to PR feedback wrt to variable name.…
TrentDaniel Jan 22, 2025
c06f858
EBP-18: Refactored implementation to improve unit testability.
TrentDaniel Jan 22, 2025
282910d
EBP-18: Added comment indicating CCSMP functions are for internal use
TrentDaniel Jan 23, 2025
d98a92c
EBP-18: Removed dead comment.
TrentDaniel Jan 23, 2025
4d9bf84
EBP-18: Added comment indicating CCSMP private interface is for use by
TrentDaniel Jan 23, 2025
e7479e5
EBP-18: Changed string constants to string literals in error messages
TrentDaniel Jan 23, 2025
c2f0ba5
EBP-18: Changed NewSolClientCacheSession to WrapSolClientCacheSessionPt.
TrentDaniel Jan 23, 2025
d929ab6
EBP-18: Fixed compiler issues and changed CancelAllPendingCacheRequests
TrentDaniel Jan 23, 2025
5bf5c87
EBP-18: Removed dead FFC comment.
TrentDaniel Jan 23, 2025
3a76eba
EBP-18: Fixed linter issues, changed type alias for CacheRequestMapIndex
TrentDaniel Jan 23, 2025
9d9cd95
EBP-18: Added doc strings, formatted files, fixed linter issues, removed
TrentDaniel Jan 23, 2025
c579833
EBP-18: Added type alias for ccsmp.CacheEventInfo to improve
TrentDaniel Jan 23, 2025
6e93006
EBP-18: Made CacheResponseProcessor implementation structs private.
TrentDaniel Jan 23, 2025
800828d
EBP-18: Removed dead error type.
TrentDaniel Jan 23, 2025
8585800
EBP-18: Fixed typo.
TrentDaniel Jan 23, 2025
9195ac2
EBP-18: Added comment to describe test helper.
TrentDaniel Jan 23, 2025
e72f1d7
EBP-18: Changed concurrent cache request test to run faster, from 17
TrentDaniel Jan 23, 2025
62dc892
EBP-18: Added resource lock to direct receiver for cache response
TrentDaniel Jan 23, 2025
31854c2
EBP-18: Formatted files, made type alias for
TrentDaniel Jan 23, 2025
527351f
EBP-18: Removed dead comment. Added further documentation to public
TrentDaniel Jan 23, 2025
f9ad1d0
EBP-18: Modified error handling so that errors from failed cache
TrentDaniel Jan 23, 2025
41c6a99
EBP-18: Fixed formatting issue.
TrentDaniel Jan 23, 2025
d8b34d1
EBP-18: Using test branch from Jenkinsfile repo to test CICD changes.
TrentDaniel Jan 24, 2025
419f4c1
EBP-18: Made changes in response to PR feedback
TrentDaniel Jan 24, 2025
55de073
EBP-18: Removed redundant log
TrentDaniel Jan 24, 2025
53847d1
EBP-18: Fixed test setup so remote containers can be used without cac…
TrentDaniel Jan 24, 2025
0a829ae
EBP-18: Fixed race condition found in testing.
TrentDaniel Jan 24, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
235 changes: 228 additions & 7 deletions internal/ccsmp/ccsmp_cache.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// pubsubplus-go-client
//
// Copyright 2021-2024 Solace Corporation. All rights reserved.
// Copyright 2025 Solace Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -14,9 +14,16 @@
// See the License for the specific language governing permissions and
// limitations under the License.

/* NOTE: The purpose of this module is to isolate interop between C and
* PSPGo. Contributions to this module should be limited to Go wrappers
* around C functions, Go types representing C data types, and of those
* must be limited to any relating directly to cache operations in C.
*/

package ccsmp

/*
#cgo CFLAGS: -DSOLCLIENT_PSPLUS_GO
#include <stdlib.h>
#include <stdio.h>

Expand All @@ -25,16 +32,230 @@ package ccsmp
#include "solclient/solClient.h"
#include "solclient/solClientMsg.h"
#include "solclient/solCache.h"
#include "./ccsmp_helper.h"

solClient_rxMsgCallback_returnCode_t messageReceiveCallback ( solClient_opaqueSession_pt opaqueSession_p, solClient_opaqueMsg_pt msg_p, void *user_p );
solClient_rxMsgCallback_returnCode_t requestResponseReplyMessageReceiveCallback ( solClient_opaqueSession_pt opaqueSession_p, solClient_opaqueMsg_pt msg_p, void *user_p );
solClient_rxMsgCallback_returnCode_t defaultMessageReceiveCallback ( solClient_opaqueSession_pt opaqueSession_p, solClient_opaqueMsg_pt msg_p, void *user_p );
void eventCallback ( solClient_opaqueSession_pt opaqueSession_p, solClient_session_eventCallbackInfo_pt eventInfo_p, void *user_p );
void handleLogCallback(solClient_log_callbackInfo_pt logInfo_p, void *user_p);

solClient_rxMsgCallback_returnCode_t flowMessageReceiveCallback ( solClient_opaqueFlow_pt opaqueFlow_p, solClient_opaqueMsg_pt msg_p, void *user_p );
solClient_rxMsgCallback_returnCode_t defaultFlowMessageReceiveCallback ( solClient_opaqueFlow_pt opaqueFlow_p, solClient_opaqueMsg_pt msg_p, void *user_p );
void flowEventCallback ( solClient_opaqueFlow_pt opaqueFlow_p, solClient_flow_eventCallbackInfo_pt eventInfo_p, void *user_p );

solClient_returnCode_t _solClient_version_set(solClient_version_info_pt version_p);
*/
RagnarPaulson marked this conversation as resolved.
Show resolved Hide resolved
import "C"
import (
"fmt"
"strconv"
"sync"
"unsafe"

"solace.dev/go/messaging/internal/impl/logging"
"solace.dev/go/messaging/pkg/solace/message"
"solace.dev/go/messaging/pkg/solace/resource"
)

// Mapping for Cached message Subscription Request Strategies to respectiveCCSMP flags
var cachedMessageSubscriptionRequestStrategyMappingToCCSMP = map[resource.CachedMessageSubscriptionStrategy]C.solClient_cacheRequestFlags_t{
resource.AsAvailable: C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_FLOWTHRU | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY,
resource.LiveCancelsCached: C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_FULFILL | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY,
resource.CachedFirst: C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_QUEUE | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY,
resource.CachedOnly: C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_FLOWTHRU | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY,
// SolClientCacheSessionPt is a type alias to the opaque cache session pointer in CCSMP.
type SolClientCacheSessionPt = C.solClient_opaqueCacheSession_pt

type SolClientCacheSession struct {
pointer SolClientCacheSessionPt
}

func (cacheSession *SolClientCacheSession) String() string {
return fmt.Sprintf("SolClientCacheSession::cache session pointer 0x%x", cacheSession.pointer)
}

func NewSolClientCacheSession(cacheSessionP SolClientCacheSessionPt) SolClientCacheSession {
return SolClientCacheSession{pointer: cacheSessionP}
}

func ConvertCachedMessageSubscriptionRequestToCcsmpPropsList(cachedMessageSubscriptionRequest resource.CachedMessageSubscriptionRequest) []string {
return []string{
SolClientCacheSessionPropCacheName, cachedMessageSubscriptionRequest.GetCacheName(),
SolClientCacheSessionPropMaxAge, strconv.FormatInt(int64(cachedMessageSubscriptionRequest.GetCachedMessageAge()), 10),
SolClientCacheSessionPropMaxMsgs, strconv.FormatInt(int64(cachedMessageSubscriptionRequest.GetMaxCachedMessages()), 10),
SolClientCacheSessionPropRequestreplyTimeoutMs, strconv.FormatInt(int64(cachedMessageSubscriptionRequest.GetCacheAccessTimeout()), 10),
}
}

// CachedMessageSubscriptionRequestStrategyMappingToCCSMPCacheRequestFlags is the mapping for Cached message Subscription Request Strategies to respective CCSMP cache request flags
var CachedMessageSubscriptionRequestStrategyMappingToCCSMPCacheRequestFlags = map[resource.CachedMessageSubscriptionStrategy]C.solClient_cacheRequestFlags_t{
resource.AsAvailable: C.solClient_cacheRequestFlags_t(C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_FLOWTHRU | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY),
resource.LiveCancelsCached: C.solClient_cacheRequestFlags_t(C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_FULFILL | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY),
resource.CachedFirst: C.solClient_cacheRequestFlags_t(C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_QUEUE | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY),
resource.CachedOnly: C.solClient_cacheRequestFlags_t(C.SOLCLIENT_CACHEREQUEST_FLAGS_LIVEDATA_FLOWTHRU | C.SOLCLIENT_CACHEREQUEST_FLAGS_NOWAIT_REPLY),
}

// CachedMessageSubscriptionRequestStrategyMappingToCCSMPSubscribeFlags is the mapping for Cached message Subscription Request Strategies to respective CCSMP subscription flags
var CachedMessageSubscriptionRequestStrategyMappingToCCSMPSubscribeFlags = map[resource.CachedMessageSubscriptionStrategy]C.solClient_subscribeFlags_t{
resource.AsAvailable: C.SOLCLIENT_SUBSCRIBE_FLAGS_REQUEST_CONFIRM,
resource.LiveCancelsCached: C.SOLCLIENT_SUBSCRIBE_FLAGS_REQUEST_CONFIRM,
resource.CachedFirst: C.SOLCLIENT_SUBSCRIBE_FLAGS_REQUEST_CONFIRM,
resource.CachedOnly: C.SOLCLIENT_SUBSCRIBE_FLAGS_LOCAL_DISPATCH_ONLY,
RagnarPaulson marked this conversation as resolved.
Show resolved Hide resolved
}

/* NOTE: sessionToCacheEventCallbackMap is required as a global var even though cache sessions etc. are scoped to a
* single receiver. This is required because the event callback that is passed to CCSMP when performing an async cache
* request cannot have a pointer into Go-managed memory by being associated with receiver. If it did, and CCSMP the
* event callback after the Go-managed receiver instance was garbage collected, the callback would point to garabage.
* If we can't have the callback syntactically associated with the receiver, we need a different way to make sure that
* a given event callback is operating on the correct receiver, since different receivers could have different
* associated data that needs to be operated on by the event callback. We use a global map which uses the user_pointer
* specific to the receiver to do this. Mapping of individual cache sessions within a receiver is done through a
* separate mechanism, and is unrelated to this one.
*
* We use only the SolClientCacheSessionPt instead of the whole SolClientCacheSession because the pt evaluates to a
* long, which can be hashed and searched faster in the map than a more complex object. At the time that this is
* implemented (1/21/2025), the SolClientSession object holds only the pt, but allows for more fields to be added in the
* future, which could greatly increase the size of the object, and so slow down hashing. Furthermore, at this time it
* is unclear what further information beyond the SolClientCacheSessionPt would be useful in indexing, which is the
* purpose of the cacheToEventCallbackMap. So, it is left as an exercise to a future developer to refactor the
* cacheToEventCallbackMap along with SolClientSession to include more information useful for indexing, as needed.
*/
var cacheToEventCallbackMap sync.Map

func (session *SolClientSession) CreateCacheSession(cacheSessionProperties []string) (SolClientCacheSession, *SolClientErrorInfoWrapper) {
cacheSessionPropertiesP, freeArrayFunc := ToCArray(cacheSessionProperties, true)
defer freeArrayFunc()
var cacheSessionP SolClientCacheSessionPt
errorInfo := handleCcsmpError(func() SolClientReturnCode {
return C.SessionCreateCacheSession(
cacheSessionPropertiesP,
session.pointer,
&cacheSessionP,
)
})
return NewSolClientCacheSession(cacheSessionP), errorInfo
}

func (cacheSession *SolClientCacheSession) ConvertPointerToInt() uintptr {
return uintptr(cacheSession.pointer)
}

func (cacheSession *SolClientCacheSession) SendCacheRequest(
dispatchID uintptr,
topic string,
cacheRequestID message.CacheRequestID,
cacheRequestFlags C.solClient_cacheRequestFlags_t, // There may be a custom type for this? TBD

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More questions to be answered here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in commit d98a92ca258db6c857cb8fd4afa3a70700f1ac0e

subscribeFlags C.solClient_subscribeFlags_t, // There may be a custom type for this? TBD
eventCallback SolClientCacheEventCallback,
) *SolClientErrorInfoWrapper {

cacheToEventCallbackMap.Store(cacheSession.pointer, eventCallback)

topicP := C.CString(topic)
errorInfo := handleCcsmpError(func() SolClientReturnCode {
return C.CacheSessionSendCacheRequest(
C.solClient_uint64_t(dispatchID),
cacheSession.pointer,
topicP,
C.solClient_uint64_t(cacheRequestID), // This is done elsewhere such as in SolClientMessgeSetSequenceNumber
cacheRequestFlags,
subscribeFlags,
)
})
return errorInfo
}

func (cacheSession *SolClientCacheSession) DestroyCacheSession() *SolClientErrorInfoWrapper {
/* NOTE: We remove the cache session pointer from the map before we destroy the cache session
* so that map access using that pointer as an index won't collide with another cache session
* that is created immediately after this one is destroyed. This should be deleted in the cache
* event callback. We delete again here in case there was a problem in the cache event callback
* so that we don't leak resources. If the entry has already been deleted, the following call
* will not fail, and will effectively be a no-op.*/
cacheToEventCallbackMap.Delete(cacheSession.pointer)

return handleCcsmpError(func() SolClientReturnCode {
return C.CacheSessionDestroy(&cacheSession.pointer)
})
}

func (cacheSession *SolClientCacheSession) CancelCacheRequest() *SolClientErrorInfoWrapper {
return handleCcsmpError(func() SolClientReturnCode {
return C.CacheSessionCancelRequests(cacheSession.pointer)
})
}

// SolClientCacheEventCallback functions format CCSMP args into Go objects and then pass those objects
// to a separate function that actually processes them
type SolClientCacheEventCallback = func(CacheEventInfo)
type CacheEventInfoPt = C.solCache_eventCallbackInfo_pt

type SolClientCacheEvent = C.solCache_event_t

type CacheEventInfo struct {
cacheSessionP SolClientCacheSessionPt
event SolClientCacheEvent
topic string
returnCode SolClientReturnCode
subCode SolClientSubCode
cacheRequestID message.CacheRequestID
err error
}

func (eventInfo *CacheEventInfo) String() string {
var errString string
if eventInfo.err != nil {
errString = eventInfo.err.Error()
} else {
errString = "nil"
}
return fmt.Sprintf("CacheEventInfo:\n\tcacheSessionP: 0x%x\n\tevent: %d\n\ttopic: %s\n\treturnCode: %d\n\tsubCode: %d\n\tcacheRequestID: %d\n\terr: %s", eventInfo.cacheSessionP, eventInfo.event, eventInfo.topic, eventInfo.returnCode, eventInfo.subCode, eventInfo.cacheRequestID, errString)
}

const (
SolClientCacheEventRequestCompletedNotice SolClientCacheEvent = 1
)

func NewCacheEventInfoForCancellation(cacheSession SolClientCacheSession, cacheRequestID message.CacheRequestID, topic string, err error) CacheEventInfo {
return CacheEventInfo{
cacheSessionP: cacheSession.pointer,
event: SolClientCacheEventRequestCompletedNotice,
topic: topic,
returnCode: SolClientReturnCodeFail,
subCode: SolClientSubCodeCacheRequestCancelled,
cacheRequestID: cacheRequestID,
err: err,
}
}

RagnarPaulson marked this conversation as resolved.
Show resolved Hide resolved
func (i *CacheEventInfo) GetCacheSessionPointer() SolClientCacheSessionPt {
return i.cacheSessionP
}

func CacheEventInfoFromCoreCacheEventInfo(eventCallbackInfo CacheEventInfoPt, userP uintptr) CacheEventInfo {
return CacheEventInfo{
cacheSessionP: SolClientCacheSessionPt(userP),
event: SolClientCacheEvent(eventCallbackInfo.cacheEvent),
topic: C.GoString(eventCallbackInfo.topic),
returnCode: SolClientReturnCode(eventCallbackInfo.rc),
subCode: SolClientSubCode(eventCallbackInfo.subCode),
cacheRequestID: message.CacheRequestID(eventCallbackInfo.cacheRequestId),
err: nil,
}
}

//export goCacheEventCallback
func goCacheEventCallback( /*opaqueSessionP*/ _ SolClientSessionPt, eventCallbackInfo CacheEventInfoPt, userP unsafe.Pointer) {
/* NOTE: We don't need to use the session pointer since we can use the user_p(a.k.a. the cache session pointer)
* which is guaranteed to be unique for at least the duration that the cache session pointer is in the global
* map since during receiver termination we destory the cache session only after we remove it from all maps.
*/
if callback, ok := cacheToEventCallbackMap.Load(SolClientCacheSessionPt(uintptr(userP))); ok {
eventInfo := CacheEventInfoFromCoreCacheEventInfo(eventCallbackInfo, uintptr(userP))
callback.(SolClientCacheEventCallback)(eventInfo)
/* NOTE: We remove the cache session pointer from the map before we destroy the cache session
* so that map access using that pointer as an index won't collide with another cache session
* that is created immediately after this one is destroyed.*/
cacheToEventCallbackMap.Delete(eventInfo.cacheSessionP)
} else {
if logging.Default.IsDebugEnabled() {
logging.Default.Debug("Received event callback from core API without an associated cache event callback")
}
}
RagnarPaulson marked this conversation as resolved.
Show resolved Hide resolved
}
2 changes: 1 addition & 1 deletion internal/ccsmp/ccsmp_cache_session_prop_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion internal/ccsmp/ccsmp_callbacks.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// pubsubplus-go-client
//
// Copyright 2021-2024 Solace Corporation. All rights reserved.
// Copyright 2021-2025 Solace Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -22,6 +22,7 @@

#include "solclient/solClient.h"
#include "solclient/solClientMsg.h"
#include "solclient/solCache.h"
#include "./ccsmp_helper.h"

solClient_rxMsgCallback_returnCode_t
Expand Down Expand Up @@ -75,3 +76,9 @@ void flowEventCallback(solClient_opaqueFlow_pt opaqueFlow_p, solClient_flow_even
void goFlowEventCallback(solClient_opaqueFlow_pt, solClient_flow_eventCallbackInfo_pt, void *);
goFlowEventCallback(opaqueFlow_p, eventInfo_p, user_p);
}

void cacheEventCallback(solClient_opaqueSession_pt opaqueSession_p, solCache_eventCallbackInfo_pt eventInfo_p, void *user_p)
{
void goCacheEventCallback(solClient_opaqueSession_pt, solCache_eventCallbackInfo_pt, void *);
RagnarPaulson marked this conversation as resolved.
Show resolved Hide resolved
goCacheEventCallback(opaqueSession_p, eventInfo_p, user_p);
}
6 changes: 5 additions & 1 deletion internal/ccsmp/ccsmp_core.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// pubsubplus-go-client
//
// Copyright 2021-2024 Solace Corporation. All rights reserved.
// Copyright 2021-2025 Solace Corporation. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
Expand All @@ -27,6 +27,7 @@ package ccsmp

#include "solclient/solClient.h"
#include "solclient/solClientMsg.h"
#include "solclient/solCache.h"
#include "./ccsmp_helper.h"

solClient_rxMsgCallback_returnCode_t messageReceiveCallback ( solClient_opaqueSession_pt opaqueSession_p, solClient_opaqueMsg_pt msg_p, void *user_p );
Expand Down Expand Up @@ -192,6 +193,9 @@ type SolClientSubCode = C.solClient_subCode_t
// SolClientSubCodeOK is assigned a value
const SolClientSubCodeOK = C.SOLCLIENT_SUBCODE_OK

// SolClientSubCodeCacheRequestCancelled is assigned a value
const SolClientSubCodeCacheRequestCancelled = C.SOLCLIENT_SUBCODE_CACHE_REQUEST_CANCELLED

RagnarPaulson marked this conversation as resolved.
Show resolved Hide resolved
// SolClientResponseCode is assigned a value
type SolClientResponseCode = C.solClient_session_responseCode_t

Expand Down
Loading
Loading