The purpose of the application is to pull the $MFT, ecnrypt it and save it on the disk. The second part of the code is decrypting it and saving it in clear form.
For some reason the decryption process is not working, I get only gibberish and apparently the same 4096 bytes repeat over and over.
I know the code is dirty, I tried to minimize it as much as possible, but if anyone spots any clear problem, please do let me know.
Thanks
char publicKey[]="-----BEGIN PUBLIC KEY-----\n"\
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAy8Dbv8prpJ/0kKhlGeJY\n"\
"ozo2t60EG8L0561g13R29LvMR5hyvGZlGJpmn65+A4xHXInJYiPuKzrKUnApeLZ+\n"\
"vw1HocOAZtWK0z3r26uA8kQYOKX9Qt/DbCdvsF9wF8gRK0ptx9M6R13NvBxvVQAp\n"\
"fc9jB9nTzphOgM4JiEYvlV8FLhg9yZovMYd6Wwf3aoXK891VQxTr/kQYoq1Yp+68\n"\
"i6T4nNq7NWC+UNVjQHxNQMQMzU6lWCX8zyg3yH88OAQkUXIXKfQ+NkvYQ1cxaMoV\n"\
"PpY72+eVthKzpMeyHkBn7ciumk5qgLTEJAfWZpe4f4eFZj/Rc8Y8Jj2IS5kVPjUy\n"\
"wQIDAQAB\n"\
"-----END PUBLIC KEY-----\n";
rsa_pkey = createRSA(publicKey,1);
if (!EVP_PKEY_assign_RSA(pkey, rsa_pkey))
{
printf("EVP_PKEY_assign_RSA: failed.\n");
return 1;
}
EVP_CIPHER_CTX_init(&ctx);
ek = malloc(EVP_PKEY_size(pkey));
if (!EVP_SealInit(&ctx, EVP_aes_128_cbc(), &ek, &eklen, iv, &pkey, 1))
{
printf("EVP_SealInit: failed.\n");
}
eklen_n = htonl(eklen);
DWORD BytesWritten2;
if(WriteFile(outputMFTfile, &eklen_n, sizeof(eklen_n), &BytesWritten2, NULL))
{
printf("Written %d bytes to the file header [EK LEN]\n",BytesWritten2);
}
if(WriteFile(outputMFTfile, ek, eklen, &BytesWritten2, NULL))
{
printf("Written %d bytes to the file header [EK]\n",BytesWritten2);
}
if(WriteFile(outputMFTfile, iv, EVP_CIPHER_iv_length(EVP_aes_128_cbc()), &BytesWritten2, NULL))
{
printf("Written %d bytes to the file header [IV]\n",BytesWritten2);
}
while (ReadFile(hRawDisk, FINAL_MFT_BUFFER, 4096, &bytesRead, NULL))
{
bytesCounter = bytesCounter+(unsigned long long)bytesRead;
if (bytesCounter<final_length)
{
if (!EVP_SealUpdate(&ctx, buffer_out, &len_out, FINAL_MFT_BUFFER, bytesRead))
{
printf("FAILED SEAL UPDATE \n");
}
WriteFile(outputMFTfile, buffer_out, len_out, &BytesWritten, NULL);
}
else
{
break;
}
}
if (!EVP_SealFinal(&ctx, buffer_out, &len_out))
{
printf("FINAL SEAL FAILED\n");
}
else
{
DWORD BytesWritten;
WriteFile(outputMFTfile, buffer_out, len_out, &BytesWritten, NULL);
}
EVP_CIPHER_CTX_cleanup(&ctx);
The following code is used for decryption(I removed the part where I provide the private key and initialize EVP):
HANDLE decryptedMFTfile=CreateFile("MFT_decrypted.dat",GENERIC_WRITE,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
DWORD BytesWritten2;
while(bytesRead>0)
{
ReadFile(encryptedMFT,buffer,sizeof(buffer),&bytesRead,NULL);
if (!EVP_OpenUpdate(&ctx, buffer_out, &len_out, buffer, len))
{
printf("EVP OPEN FAILED \n");
}
WriteFile(decryptedMFTfile, &buffer_out, sizeof(buffer_out), &BytesWritten2, NULL);
}
CreateRsa code on request:
RSA * createRSA(unsigned char * key,int public)
{
RSA *rsa= NULL;
BIO *keybio ;
keybio = BIO_new_mem_buf(key, -1);
if (keybio==NULL)
{
printf( "Failed to create key BIO");
return 0;
}
if(public)
{
rsa = PEM_read_bio_RSA_PUBKEY(keybio, &rsa,NULL, NULL);
}
else
{
rsa = PEM_read_bio_RSAPrivateKey(keybio, &rsa,NULL, NULL);
}
if(rsa == NULL)
{
printf( "Failed to create RSA");
}
return rsa;
}
Related
I have the following code which reads a video files and extracts motion vectors from each frame and outputs them to stdout:
#include <libavutil/motion_vector.h>
#include <libavformat/avformat.h>
static AVFormatContext *fmt_ctx = NULL;
static AVCodecContext *video_dec_ctx = NULL;
static AVStream *video_stream = NULL;
static const char *src_filename = NULL;
static int video_stream_idx = -1;
static AVFrame *frame = NULL;
static int video_frame_count = 0;
static int decode_packet(const AVPacket *pkt)
{
//fprintf(stderr, "%d\n", video_dec_ctx->width);
int ret = avcodec_send_packet(video_dec_ctx, pkt);
if (ret < 0) {
fprintf(stderr, "Error while sending a packet to the decoder: %s\n", av_err2str(ret));
return ret;
}
while (ret >= 0) {
ret = avcodec_receive_frame(video_dec_ctx, frame);
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
break;
} else if (ret < 0) {
fprintf(stderr, "Error while receiving a frame from the decoder: %s\n", av_err2str(ret));
return ret;
}
if (ret >= 0) {
int i;
AVFrameSideData *sd;
video_frame_count++;
sd = av_frame_get_side_data(frame, AV_FRAME_DATA_MOTION_VECTORS);
if (sd) {
const AVMotionVector *mvs = (const AVMotionVector *)sd->data;
for (i = 0; i < sd->size / sizeof(*mvs); i++) {
const AVMotionVector *mv = &mvs[i];
printf("%d,%4d,%4d,%4d\n",
video_frame_count,
abs(mv->src_x - mv->dst_x),
abs(mv->src_y - mv->dst_y));
}
}
av_frame_unref(frame);
}
}
return 0;
}
static int open_codec_context(AVFormatContext *fmt_ctx, enum AVMediaType type)
{
int ret;
AVStream *st;
AVCodecContext *dec_ctx = NULL;
AVCodec *dec = NULL;
AVDictionary *opts = NULL;
ret = av_find_best_stream(fmt_ctx, type, -1, -1, &dec, 0);
if (ret < 0) {
fprintf(stderr, "Could not find %s stream in input file '%s'\n",
av_get_media_type_string(type), src_filename);
return ret;
} else {
int stream_idx = ret;
st = fmt_ctx->streams[stream_idx];
dec_ctx = avcodec_alloc_context3(dec);
if (!dec_ctx) {
fprintf(stderr, "Failed to allocate codec\n");
return AVERROR(EINVAL);
}
ret = avcodec_parameters_to_context(dec_ctx, st->codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters to codec context\n");
return ret;
}
/* Init the video decoder */
av_dict_set(&opts, "flags2", "+export_mvs", 0);
if ((ret = avcodec_open2(dec_ctx, dec, &opts)) < 0) {
fprintf(stderr, "Failed to open %s codec\n",
av_get_media_type_string(type));
return ret;
}
video_stream_idx = stream_idx;
video_stream = fmt_ctx->streams[video_stream_idx];
video_dec_ctx = dec_ctx;
}
return 0;
}
int main(int argc, char **argv)
{
int ret = 0;
AVPacket pkt = { 0 };
if (argc != 2) {
fprintf(stderr, "Usage: %s <video>\n", argv[0]);
exit(1);
}
src_filename = argv[1];
if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
fprintf(stderr, "Could not open source file %s\n", src_filename);
exit(1);
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
fprintf(stderr, "Could not find stream information\n");
exit(1);
}
open_codec_context(fmt_ctx, AVMEDIA_TYPE_VIDEO);
av_dump_format(fmt_ctx, 0, src_filename, 0);
if (!video_stream) {
fprintf(stderr, "Could not find video stream in the input, aborting\n");
ret = 1;
goto end;
}
frame = av_frame_alloc();
if (!frame) {
fprintf(stderr, "Could not allocate frame\n");
ret = AVERROR(ENOMEM);
goto end;
}
printf("framenum,dx,dy\n");
/* read frames from the file */
while (av_read_frame(fmt_ctx, &pkt) >= 0) {
if (pkt.stream_index == video_stream_idx)
ret = decode_packet(&pkt);
av_packet_unref(&pkt);
if (ret < 0)
break;
}
/* flush cached frames */
decode_packet(NULL);
end:
avcodec_free_context(&video_dec_ctx);
avformat_close_input(&fmt_ctx);
av_frame_free(&frame);
return ret < 0;
}
Instead and outputting every frame, I only want to read one frame every 60 frames. The speed of the program is very important so I want to eliminate as much unnecessary code as possible. The problem is I'm having a hard time understanding how to advance the video stream without reading in new frames. I call av_read_frame(fmt_ctx, &pkt) to get packet info that is decoded by decode_packet and then goes on to read the next, but I can't find a way to only get packet info for one frame out of each 60.
Use av_seek_frame() to read every 60th frame.
I'm doing Kaa project.
First, i have used Data Collection demo and then do some modification to my sensor. The data is send to platform every seconds(i can configure it).
And now, i want to create some service which if i send request(using ulfius framework), my device upload 1 data.
My problem is, i have to do more than 10 request to upload data, how can i solve it. Please Help.
Below is some of my code :
int callback_kaa(const struct _u_request * request,struct _u_response * response, void * user_data){
kaa_client_send(kaa_client, &kaa_add_log_record, (void *)kaa_client);
ulfius_set_string_body_response(response, 200, "Hello Kaa!");}
int main(/*int argc, char *argv[]*/){
printf("DHT11 Logging Demo\n");
if (wiringPiSetup() == -1) {
printf("Failed to initialize Pi wiring\n");
exit(1);}
if (ulfius_init_instance(&instance, PORT, NULL, NULL) != U_OK) {
fprintf(stderr, "Error ulfius_init_instance, abort\n");
return(1);}
ulfius_add_endpoint_by_val(&instance, "POST", "/kaaplatform", NULL, 0,&callback_kaa, NULL);
kaa_error_t error_code = kaa_client_create(&kaa_client, NULL);
KAA_RETURN_IF_ERROR(error_code, "Failed create Kaa client");
kaa_log_bucket_constraints_t bucket_sizes = {
.max_bucket_size = MAX_LOG_BUCKET_SIZE,
.max_bucket_log_count = 1,};
error_code = ext_unlimited_log_storage_create(&log_storage_context,
kaa_client_get_context(kaa_client)->logger);
KAA_RETURN_IF_ERROR(error_code, "Failed to create unlimited log storage");
error_code = ext_log_upload_strategy_create(kaa_client_get_context(kaa_client), &log_upload_strategy_context, THRESHOLD_COUNT_FLAG);
KAA_RETURN_IF_ERROR(error_code, "Failed to create log upload strategy");
error_code = ext_log_upload_strategy_set_threshold_count(log_upload_strategy_context, KAA_UPLOAD_COUNT_THRESHOLD);
KAA_RETURN_IF_ERROR(error_code, "Failed to set threshold log record count");
error_code = kaa_logging_init(kaa_client_get_context(kaa_client)>log_collector
, log_storage_context
, log_upload_strategy_context
, &bucket_sizes);
KAA_RETURN_IF_ERROR(error_code, "Failed to init Kaa log collector");
kaa_channel_manager_set_auth_failure_handler(
kaa_client_get_context(kaa_client)->channel_manager,
auth_failure_handler, kaa_client);
const uint8_t *endpoint_key_hash = NULL;
size_t endpoint_key_hash_length = 0;
ext_get_sha1_base64_public(&endpoint_key_hash, &endpoint_key_hash_length);
printf("Endpoint Key Hash: %.*s\n", (int)endpoint_key_hash_length,endpoint_key_hash);
if (ulfius_start_framework(&instance) == U_OK) {
printf("Start framework on port %d\n", instance.port);
getchar();
} else {
fprintf(stderr, "Error starting framework\n");
}
kaa_client_destroy(kaa_client);
printf("Data analytics demo stopped\n");
ulfius_stop_framework(&instance);
ulfius_clean_instance(&instance);
return error_code;
}
and also, i create some function (kaa_client_send) at file kaa_client.h :
void kaa_client_send(kaa_client_t *kaa_client,external_process_fn external_process, void *external_process_context)
{
KAA_RETURN_IF_NIL(kaa_client, KAA_ERR_BADPARAM);
kaa_error_t error_code = kaa_check_readiness(kaa_client->kaa_context);
if (error_code != KAA_ERR_NONE) {
KAA_LOG_ERROR(kaa_client->kaa_context->logger, error_code, "Cannot start Kaa client: Kaa context is not fully initialized");
return error_code;
}
kaa_client->external_process_fn = external_process;
kaa_client->external_process_context = external_process_context;
kaa_client->external_process_max_delay = 5;
kaa_client->external_process_last_call = KAA_TIME();
KAA_LOG_INFO(kaa_client->kaa_context->logger, KAA_ERR_NONE, "Starting Kaa client...");
if (kaa_client->external_process_fn) {
kaa_client->external_process_fn(kaa_client->external_process_context);
kaa_client->external_process_last_call = KAA_TIME();
}
//Check Kaa channel is ready to transmit something
if (kaa_process_failover(kaa_client->kaa_context)) {
kaa_client->boostrap_complete = false;
} else {
if (kaa_client->channel_id > 0) {
if (kaa_client->channel_state == KAA_CLIENT_CHANNEL_STATE_NOT_CONNECTED) {
error_code = kaa_client_process_channel_disconnected(kaa_client);
} else if (kaa_client->channel_state == KAA_CLIENT_CHANNEL_STATE_CONNECTED) {
error_code = kaa_client_process_channel_connected(kaa_client);
if (error_code == KAA_ERR_TIMEOUT)
kaa_client_deinit_channel(kaa_client);
}
} else {
//No initialized channels
if (kaa_client->boostrap_complete) {
KAA_LOG_INFO(kaa_client->kaa_context->logger, KAA_ERR_NONE,
"Channel [0x%08X] Boostrap complete, reinitializing to Operations ...", kaa_client->channel_id);
kaa_client->boostrap_complete = false;
kaa_client_deinit_channel(kaa_client);
error_code = kaa_client_init_channel(kaa_client, KAA_CLIENT_CHANNEL_TYPE_OPERATIONS);
if (error_code == KAA_ERR_BAD_STATE) {
kaa_client_deinit_channel(kaa_client);
kaa_client->boostrap_complete = false;
}
} else {
KAA_LOG_INFO(kaa_client->kaa_context->logger, KAA_ERR_NONE,
"Channel [0x%08X] Operations error, reinitializing to Bootstrap ...", kaa_client->channel_id);
kaa_client->boostrap_complete = true;
kaa_client_deinit_channel(kaa_client);
kaa_client_init_channel(kaa_client, KAA_CLIENT_CHANNEL_TYPE_BOOTSTRAP);
}
}
}
KAA_LOG_INFO(kaa_client->kaa_context->logger, KAA_ERR_NONE, "Kaa client stopped");
return error_code;
}
I've followed the advice on many other questions regarding this, and put something together, but my code doesn't work. It fails at X509_verify_cert() and ERR_error_string(ERR_get_error(), NULL) results in:
error:0B07F069:x509 certificate routines:X509_verify_cert:no cert set for us to verify
Here's my code:
int tallis_ssl_verify(tallis_t *tallis, X509 *cert, X509 *CA)
{
int rv;
X509_VERIFY_PARAM_set_hostflags(
tallis->param,
X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS);
X509_VERIFY_PARAM_set1_host(tallis->param, tallis->host, 0);
SSL_CTX_set_verify(tallis->ssl_context, SSL_VERIFY_PEER, NULL);
SSL_set_verify(tallis->ssl_connection, SSL_VERIFY_PEER, NULL);
ERR_clear_error();
rv = SSL_get_verify_result(tallis->ssl_connection);
if (rv != X509_V_OK)
return 1;
ERR_clear_error();
X509_STORE_CTX *ctx = X509_STORE_CTX_new();
X509_STORE *store = X509_STORE_new();
X509_STORE_CTX_init(ctx, store, cert, NULL);
X509_STORE_set_flags(store, X509_V_FLAG_CB_ISSUER_CHECK);
X509_LOOKUP *lookup = X509_STORE_add_lookup(store, X509_LOOKUP_file());
X509_STORE_load_locations(
store,
"/etc/ssl/certs/UTN_USERFirst_Hardware_Root_CA.pem",
NULL);
X509_STORE_set_default_paths(store);
X509_LOOKUP_load_file(
lookup,
"/etc/ssl/certs/UTN_USERFirst_Hardware_Root_CA.pem",
X509_FILETYPE_PEM);
X509_STORE_add_cert(store, cert);
if (!store)
{
X509_STORE_free(store);
return 1;
}
SSL_CTX_set_default_verify_paths(tallis->ssl_context);
ERR_clear_error();
rv = SSL_CTX_load_verify_locations(
tallis->ssl_context,
"/etc/ssl/certs/UTN_USERFirst_Hardware_Root_CA.pem",
"/etc/ssl/certs");
if (!rv)
{
fprintf(stderr, ERR_error_string(ERR_get_error(), NULL));
return 1;
}
ERR_clear_error();
rv = X509_verify_cert(ctx);
if (rv != 1)
{
fprintf(
stderr,
"%s\n%s\n",
ERR_error_string(ERR_get_error(), NULL),
X509_verify_cert_error_string(ctx->error));
return 1;
}
return 0;
}
I am trying to play a video file over self created wayland surface but rendering is not happening. I took reference from
https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-base-libs/html/gst-plugins-base-libs-gstvideooverlay.html
and
https://git.collabora.com/cgit/user/gkiagia/gst-wayland-gtk-demo.git/tree/main.c
After creation of pipeline (filesrc->parser->decoder->waylandsink); I am setting bus_set_sync_handler as
gst_bus_set_sync_handler (bus, (GstBusSyncHandler) create_window_own,
gstreamerData.pipeline, NULL);
Implementation of create_window_own is below
static GstBusSyncReply create_window_own (GstBus * bus, GstMessage * message, GstPipeline * pipeline)
{
if (counter == 0)
{
display = wl_display_connect(NULL);
if (display == NULL)
{
fprintf(stderr, "Can't connect to display\n");
exit(1);
}
printf("connected to display\n");
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_dispatch(display);
wl_display_roundtrip(display);
if (compositor == NULL) {
fprintf(stderr, "Can't find compositor\n");
exit(1);
} else {
fprintf(stderr, "Found compositor\n");
}
surface = wl_compositor_create_surface(compositor);
if (surface == NULL) {
fprintf(stderr, "Can't create surface\n");
exit(1);
} else {
fprintf(stderr, "Created surface\n");
}
shell_surface = wl_shell_get_shell_surface(shell, surface);
if (shell_surface == NULL) {
fprintf(stderr, "Can't create shell surface\n");
exit(1);
} else {
fprintf(stderr, "Created shell surface\n");
}
wl_shell_surface_set_toplevel(shell_surface);
wl_shell_surface_add_listener(shell_surface,
&shell_surface_listener, NULL);
create_window();
//paint_pixels();
wl_surface_attach(surface, buffer, 0, 0);
//wl_surface_attach(surface, NULL, 0, 0);
//wl_surface_damage(surface, 0, 0, WIDTH, HEIGHT);
//wl_surface_commit(surface);
printf("Before Thread\n");
pthread_create(&tid, NULL, myThreadFun, (void*)display);
printf("After Thread\n");
counter = 1;
}
if (gst_is_wayland_display_handle_need_context_message (message)) {
if (display != 0) {
GstContext *context;
printf("getting context\n");
context = gst_wayland_display_handle_context_new (display);
gst_element_set_context(GST_ELEMENT (GST_MESSAGE_SRC (message)), context);
/* HACK save the pointer to the sink (which implements GstWaylandVideo)
* from this point. Unfortunately, d->overlay can also be the playbin
* instead of waylandsink */
wlvideo = GST_WAYLAND_VIDEO (GST_MESSAGE_SRC (message));
} else {
g_warning ("Should have obtained display_handle by now!\n");
}
gst_message_unref (message);
return GST_BUS_DROP;
} else if (gst_is_video_overlay_prepare_window_handle_message (message)) {
if (surface != 0) {
/* GST_MESSAGE_SRC (message) will be the overlay object that we have to
* use. This may be waylandsink, but it may also be playbin. In the latter
* case, we must make sure to use playbin instead of waylandsink, because
* playbin resets the window handle and render_rectangle after restarting
* playback and the actual window size is lost */
overlay = GST_VIDEO_OVERLAY (GST_MESSAGE_SRC (message));
/*
g_print ("setting window handle and size (%d x %d)\n",
d->video_widget_allocation.width, d->video_widget_allocation.height);
*/
gst_video_overlay_set_window_handle (overlay, (guintptr) surface);
if (wlvideo && overlay ) {
gst_wayland_video_begin_geometry_change (wlvideo);
printf("setting rendrer rectangle\n");
gst_video_overlay_set_render_rectangle (overlay,
0, 0,
800, 480);
} else {
g_warning ("Should have obtained surface by now!\n");
}
gst_message_unref (message);
return GST_BUS_DROP;
}
}
printf("+++ret\n");
return GST_BUS_PASS;
}
set after create window
wl_shell_surface_set_fullscreen(shell_surface,
WL_SHELL_SURFACE_FULLSCREEN_METHOD_DEFAULT,
0, NULL);
I'm having trouble with a function I made to create a sha1 hash of some strings. The function generates a valid hash for strings longer than 4 chars, but for chars less than that the hash is wrong. I've been looking all over for a solution but I can't see whats wrong.
int create_hash(char *plaintext, char *digest) {
char digest_buff[3];
DWORD buf_size = SHA1_HASH_LEN;
HCRYPTPROV prov;
HCRYPTHASH hash;
BYTE rgbHash[SHA1_HASH_LEN];
CHAR rgbDigits[] = "0123456789abcdef";
int i;
if(!CryptAcquireContext(&prov, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
if(DEBUG) {
printf("CryptAcquireContext failed: %d\n", GetLastError());
}
return SHA1_FAILED;
}
if(!CryptCreateHash(prov, CALG_SHA, NULL, NULL, &hash)) {
if(DEBUG) {
printf("CryptCreateHash failed: %d\n", GetLastError());
}
return SHA1_FAILED;
}
if(!CryptHashData(hash, (BYTE *)plaintext, sizeof(hash), NULL)) {
if(DEBUG) {
printf("CryptHashData failed: %d\n", GetLastError());
}
return SHA1_FAILED;
}
if(!CryptGetHashParam(hash, HP_HASHVAL, rgbHash, &buf_size, NULL)) {
if(DEBUG) {
printf("CryptGetHashParam failed: %d\n", GetLastError());
}
return SHA1_FAILED;
}
for(i=0; i < buf_size; i++) {
sprintf(digest_buff, "%c%c", rgbDigits[rgbHash[i] >> 4], rgbDigits[rgbHash[i] & 0xf]);
strcat(digest, digest_buff);
}
if(!CryptDestroyHash(hash)) {
if(DEBUG) {
printf("CryptDestroyHash failed: %d\n", GetLastError());
}
return SHA1_FAILED;
}
if(!CryptReleaseContext(prov, NULL)) {
if(DEBUG) {
printf("CryptReleaseContext failed: %d\n", GetLastError());
}
return SHA1_FAILED;
}
if(DEBUG) {
printf("digest: %s\n", digest);
}
return 0;
}
CryptHashData accepts the length of the data as third parameter, and you pass size of the hash buffer there. You need to pass smth like strlen(plaintext) there.