TPM efforts

This commit is contained in:
emanuel 2022-06-14 12:55:44 +01:00
parent e91d082e39
commit ace3bcecc1
4 changed files with 183 additions and 100 deletions

View File

@ -314,7 +314,7 @@ int facilities_config(void* facilities_s) {
syslog_err("[facilities] [config] unrecognized tolling protocol, defaulting to 'simple'");
facilities->tolling.protocol = TOLLING_PROTOCOL_SIMPLE;
}
facilities->tolling.client_id = config->facilities.tpm.client_id;
facilities->tolling.station.obu.client_id = config->facilities.tpm.client_id;
// PCM
facilities->coordination.active = config->facilities.dcm.activate;

View File

@ -17,7 +17,7 @@ static void tcp_conn_reset(facilities_t* facilities, TCPConnRSTInfo_t* cri, void
if (!memcmp(bulletin->to_consume[i]->endpoint.ipv6_addr, cri->destinationAddress.buf, 16)) {
sreq = calloc(1, sizeof(SecurityRequest_t));
sreq->present = SecurityRequest_PR_tlsReset;
sreq->choice.tlsReset.connId = tolling->tls_conn_id;
sreq->choice.tlsReset.connId = tolling->station.obu.tls_conn_id;
uint8_t b_s[64];
b_s[0] = 4;

256
src/tpm.c
View File

@ -56,10 +56,10 @@ int tpm_pay(void* fc, tolling_info_t* info, void* security_socket, uint8_t* neig
goto cleanup;
}
tolling->active = true;
tolling->nonce = rand() + 1;
tolling->station.obu.active = true;
tolling->station.obu.nonce = rand() + 1;
syslog_info("[facilities] [tolling] issuing toll payment > client: %ld | nonce: %ld", tolling->client_id, tolling->nonce);
syslog_info("[facilities] [tolling] issuing toll payment > client: %ld | nonce: %ld", tolling->station.obu.client_id, tolling->station.obu.nonce);
// TPM
tpm = calloc(1, sizeof(TPM_t));
@ -70,46 +70,62 @@ int tpm_pay(void* fc, tolling_info_t* info, void* security_socket, uint8_t* neig
tpm->header.stationID = facilities->id.station_id;
pthread_mutex_unlock(&facilities->id.lock);
tpm->tpm = calloc(1, sizeof(TollingPaymentMessage_t));
// timestamp
asn_ulong2INTEGER(&tpm->tpm.timestamp, it2s_tender_get_clock(&facilities->epv));
asn_ulong2INTEGER(&tpm->tpm->timestamp, it2s_tender_get_clock(&facilities->epv));
// stationType
tpm->tpm.stationType = facilities->station_type;
tpm->tpm->stationType = facilities->station_type;
// referencePosition
it2s_tender_lock_space(&facilities->epv);
it2s_tender_get_space(&facilities->epv);
tpm->tpm.referencePosition.altitude.altitudeValue = facilities->epv.space.altitude;
tpm->tpm.referencePosition.altitude.altitudeConfidence = facilities->epv.space.altitude_conf;
tpm->tpm.referencePosition.latitude = facilities->epv.space.latitude;
tpm->tpm.referencePosition.longitude = facilities->epv.space.longitude;
tpm->tpm->referencePosition.altitude.altitudeValue = facilities->epv.space.altitude;
tpm->tpm->referencePosition.altitude.altitudeConfidence = facilities->epv.space.altitude_conf;
tpm->tpm->referencePosition.latitude = facilities->epv.space.latitude;
tpm->tpm->referencePosition.longitude = facilities->epv.space.longitude;
uint16_t lat_conf = facilities->epv.space.latitude_conf;
uint16_t lon_conf = facilities->epv.space.longitude_conf;
it2s_tender_unlock_space(&facilities->epv);
if (lat_conf > lon_conf) {
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorOrientation = 0;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorOrientation = 0;
} else {
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorOrientation = 900;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorOrientation = 900;
}
tpm->tpm.tollingType = calloc(1, sizeof(TollingType_t));
TollingType_t* type = tpm->tpm.tollingType;
tpm->tpm->tollingType = calloc(1, sizeof(TollingType_t));
TollingType_t* type = tpm->tpm->tollingType;
tolling->station.obu.toll_type = info->asn->tollType;
switch (info->asn->tollType) {
case TollType_entry:
type->present = TollingType_PR_entry;
type->choice.entry.present = TollingEntry_PR_request;
type->choice.entry.choice.request.clientId = tolling->station.obu.client_id;
type->choice.entry.choice.request.infoId = info->asn->id;
type->choice.entry.choice.request.transactionNonce = tolling->station.obu.nonce;
break;
case TollType_exit:
type->present = TollingType_PR_exit;
type->choice.exit = calloc(1, sizeof(TollingExit_t));
type->choice.exit->present = TollingExit_PR_request;
type->choice.exit->choice.request = calloc(1, sizeof(TollingExitRequest_t));
type->choice.exit->choice.request->clientId = tolling->station.obu.client_id;
type->choice.exit->choice.request->infoId = info->asn->id;
type->choice.exit->choice.request->transactionNonce = tolling->station.obu.nonce;
//type->choice.exit->choice.request->entryProof = calloc(1, sizeof(TollingEntryProof_t));
break;
case TollType_single:
type->present = TollingType_PR_single;
type->choice.single.present = TollSingle_PR_request;
type->choice.single.choice.request.clientId = tolling->client_id;
type->choice.single.present = TollingSingle_PR_request;
type->choice.single.choice.request.clientId = tolling->station.obu.client_id;
type->choice.single.choice.request.infoId = info->asn->id;
type->choice.single.choice.request.transactionNonce = tolling->nonce;
type->choice.single.choice.request.transactionNonce = tolling->station.obu.nonce;
break;
}
@ -213,10 +229,10 @@ int tpm_pay(void* fc, tolling_info_t* info, void* security_socket, uint8_t* neig
sreq->choice.tlsSend.data.size = tpm_uper_len;
memcpy(sreq->choice.tlsSend.data.buf, tpm_uper, tpm_uper_len);
id = rand() + 1;
if (!tolling->tls_conn_id) {
tolling->tls_conn_id = id;
if (!tolling->station.obu.tls_conn_id) {
tolling->station.obu.tls_conn_id = id;
}
sreq->choice.tlsSend.connId = tolling->tls_conn_id;
sreq->choice.tlsSend.connId = tolling->station.obu.tls_conn_id;
buf[0] = 4;
asn_enc_rval_t enc = oer_encode_to_buffer(&asn_DEF_SecurityRequest, NULL, sreq, buf+1, buf_len-1);
@ -334,18 +350,18 @@ cleanup:
static void rsu_handle_recv(facilities_t* facilities, TPM_t* tpm_rx, void* security_socket, uint8_t* neighbour, uint8_t* src_addr) {
if (!tpm_rx->tpm.tollingType) {
if (!tpm_rx->tpm->tollingType) {
syslog_err("[facilities] [tolling] received TPM does not have a type");
return;
}
TollingType_t* type_rx = tpm_rx->tpm.tollingType;
TollingType_t* type_rx = tpm_rx->tpm->tollingType;
uint64_t client_id, nonce, info_id;
switch (type_rx->present) {
case TollingType_PR_entry:
if (type_rx->choice.entry.present != TollEntry_PR_request) {
if (type_rx->choice.entry.present != TollingEntry_PR_request) {
syslog_err("[facilities] [tolling] received TPM.entry is not request");
return;
}
@ -355,7 +371,7 @@ static void rsu_handle_recv(facilities_t* facilities, TPM_t* tpm_rx, void* secur
break;
case TollingType_PR_exit:
if (!type_rx->choice.exit ||
type_rx->choice.exit->present != TollExit_PR_request ||
type_rx->choice.exit->present != TollingExit_PR_request ||
!type_rx->choice.exit->choice.request
) {
syslog_err("[facilities] [tolling] received TPM.exit is not request");
@ -372,7 +388,7 @@ static void rsu_handle_recv(facilities_t* facilities, TPM_t* tpm_rx, void* secur
break;
case TollingType_PR_single:
if (type_rx->choice.single.present != TollSingle_PR_request) {
if (type_rx->choice.single.present != TollingSingle_PR_request) {
syslog_err("[facilities] [tolling] received TPM.single is not request");
return;
}
@ -490,58 +506,60 @@ static void rsu_handle_recv(facilities_t* facilities, TPM_t* tpm_rx, void* secur
tpm->header.stationID = facilities->id.station_id;
pthread_mutex_unlock(&facilities->id.lock);
tpm->tpm = calloc(1, sizeof(TollingPaymentMessage_t));
// timestamp
asn_ulong2INTEGER(&tpm->tpm.timestamp, it2s_tender_get_clock(&facilities->epv));
asn_ulong2INTEGER(&tpm->tpm->timestamp, it2s_tender_get_clock(&facilities->epv));
// stationType
tpm->tpm.stationType = facilities->station_type;
tpm->tpm->stationType = facilities->station_type;
// referencePosition
it2s_tender_lock_space(&facilities->epv);
it2s_tender_get_space(&facilities->epv);
tpm->tpm.referencePosition.altitude.altitudeValue = facilities->epv.space.altitude;
tpm->tpm.referencePosition.altitude.altitudeConfidence = facilities->epv.space.altitude_conf;
tpm->tpm.referencePosition.latitude = facilities->epv.space.latitude;
tpm->tpm.referencePosition.longitude = facilities->epv.space.longitude;
tpm->tpm->referencePosition.altitude.altitudeValue = facilities->epv.space.altitude;
tpm->tpm->referencePosition.altitude.altitudeConfidence = facilities->epv.space.altitude_conf;
tpm->tpm->referencePosition.latitude = facilities->epv.space.latitude;
tpm->tpm->referencePosition.longitude = facilities->epv.space.longitude;
uint16_t lat_conf = facilities->epv.space.latitude_conf;
uint16_t lon_conf = facilities->epv.space.longitude_conf;
it2s_tender_unlock_space(&facilities->epv);
if (lat_conf > lon_conf) {
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorOrientation = 0;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorOrientation = 0;
} else {
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm.referencePosition.positionConfidenceEllipse.semiMajorOrientation = 900;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMinorConfidence = lon_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorConfidence = lat_conf;
tpm->tpm->referencePosition.positionConfidenceEllipse.semiMajorOrientation = 900;
}
tpm->tpm.tollingType = calloc(1, sizeof(TollingType_t));
switch (tpm_rx->tpm.tollingType->present) {
tpm->tpm->tollingType = calloc(1, sizeof(TollingType_t));
switch (tpm_rx->tpm->tollingType->present) {
case TollingType_PR_entry:
tpm->tpm.tollingType->present = TollingType_PR_entry;
tpm->tpm.tollingType->choice.entry.present = TollEntry_PR_reply;
tpm->tpm.tollingType->choice.entry.choice.reply.clientId = client_id;
tpm->tpm.tollingType->choice.entry.choice.reply.infoId = info_id;
tpm->tpm.tollingType->choice.entry.choice.reply.transactionNonce = nonce;
tpm->tpm.tollingType->choice.entry.choice.reply.confirmationCode = TollConfirmationCode_accepted;
tpm->tpm->tollingType->present = TollingType_PR_entry;
tpm->tpm->tollingType->choice.entry.present = TollingEntry_PR_reply;
tpm->tpm->tollingType->choice.entry.choice.reply.clientId = client_id;
tpm->tpm->tollingType->choice.entry.choice.reply.infoId = info_id;
tpm->tpm->tollingType->choice.entry.choice.reply.transactionNonce = nonce;
tpm->tpm->tollingType->choice.entry.choice.reply.confirmationCode = TollingConfirmationCode_accepted;
break;
case TollingType_PR_exit:
tpm->tpm.tollingType->present = TollingType_PR_exit;
tpm->tpm.tollingType->choice.exit = calloc(1, sizeof(TollExit_t));
tpm->tpm.tollingType->choice.exit->present = TollExit_PR_reply;
tpm->tpm.tollingType->choice.exit->choice.reply.clientId = client_id;
tpm->tpm.tollingType->choice.exit->choice.reply.infoId = info_id;
tpm->tpm.tollingType->choice.exit->choice.reply.transactionNonce = nonce;
tpm->tpm.tollingType->choice.exit->choice.reply.confirmationCode = TollConfirmationCode_accepted;
tpm->tpm->tollingType->present = TollingType_PR_exit;
tpm->tpm->tollingType->choice.exit = calloc(1, sizeof(TollingExit_t));
tpm->tpm->tollingType->choice.exit->present = TollingExit_PR_reply;
tpm->tpm->tollingType->choice.exit->choice.reply.clientId = client_id;
tpm->tpm->tollingType->choice.exit->choice.reply.infoId = info_id;
tpm->tpm->tollingType->choice.exit->choice.reply.transactionNonce = nonce;
tpm->tpm->tollingType->choice.exit->choice.reply.confirmationCode = TollingConfirmationCode_accepted;
break;
case TollingType_PR_single:
tpm->tpm.tollingType->present = TollingType_PR_single;
tpm->tpm.tollingType->choice.single.present = TollSingle_PR_reply;
tpm->tpm.tollingType->choice.single.choice.reply.clientId = client_id;
tpm->tpm.tollingType->choice.single.choice.reply.infoId = info_id;
tpm->tpm.tollingType->choice.single.choice.reply.transactionNonce = nonce;
tpm->tpm.tollingType->choice.single.choice.reply.confirmationCode = TollConfirmationCode_accepted;
tpm->tpm->tollingType->present = TollingType_PR_single;
tpm->tpm->tollingType->choice.single.present = TollingSingle_PR_reply;
tpm->tpm->tollingType->choice.single.choice.reply.clientId = client_id;
tpm->tpm->tollingType->choice.single.choice.reply.infoId = info_id;
tpm->tpm->tollingType->choice.single.choice.reply.transactionNonce = nonce;
tpm->tpm->tollingType->choice.single.choice.reply.confirmationCode = TollingConfirmationCode_accepted;
break;
default:
break;
@ -648,10 +666,10 @@ static void rsu_handle_recv(facilities_t* facilities, TPM_t* tpm_rx, void* secur
memcpy(sreq->choice.tlsSend.data.buf, tpm_uper, tpm_uper_len);
id = rand() + 1;
// TODO handle various vehicles
if (!tolling->tls_conn_id) {
tolling->tls_conn_id = id;
if (!tolling->station.obu.tls_conn_id) {
tolling->station.obu.tls_conn_id = id;
}
sreq->choice.tlsSend.connId = tolling->tls_conn_id;
sreq->choice.tlsSend.connId = tolling->station.obu.tls_conn_id;
buf[0] = 4;
asn_enc_rval_t enc = oer_encode_to_buffer(&asn_DEF_SecurityRequest, NULL, sreq, buf+1, buf_len-1);
@ -762,19 +780,19 @@ cleanup:
static void veh_handle_recv(tolling_t* tolling, TPM_t* tpm_rx, void* security_socket, uint8_t* neighbour) {
if (!tpm_rx->tpm.tollingType) {
if (!tpm_rx->tpm->tollingType) {
syslog_err("[facilities] [tolling] received TPM does not have a type");
return;
}
TollingType_t* type_rx = tpm_rx->tpm.tollingType;
TollingType_t* type_rx = tpm_rx->tpm->tollingType;
uint64_t client_id, nonce, info_id;
int confirmation_code;
switch (type_rx->present) {
case TollingType_PR_entry:
if (type_rx->choice.entry.present != TollEntry_PR_reply) {
if (type_rx->choice.entry.present != TollingEntry_PR_reply) {
syslog_err("[facilities] [tolling] received TPM.entry is not reply");
return;
}
@ -785,7 +803,7 @@ static void veh_handle_recv(tolling_t* tolling, TPM_t* tpm_rx, void* security_so
break;
case TollingType_PR_exit:
if (!type_rx->choice.exit ||
type_rx->choice.exit->present != TollExit_PR_reply
type_rx->choice.exit->present != TollingExit_PR_reply
) {
syslog_err("[facilities] [tolling] received TPM.exit is not reply");
return;
@ -796,7 +814,7 @@ static void veh_handle_recv(tolling_t* tolling, TPM_t* tpm_rx, void* security_so
confirmation_code = type_rx->choice.exit->choice.reply.confirmationCode;
break;
case TollingType_PR_single:
if (type_rx->choice.single.present != TollSingle_PR_reply) {
if (type_rx->choice.single.present != TollingSingle_PR_reply) {
syslog_err("[facilities] [tolling] received TPM.single is not reply");
return;
}
@ -812,12 +830,12 @@ static void veh_handle_recv(tolling_t* tolling, TPM_t* tpm_rx, void* security_so
// TODO if sent ENTRY.REQUEST expect ENTRY.REPLY, if send SINGLE.REQUEST expect ENTRY.REPLY, etc
if (client_id != tolling->client_id) {
if (client_id != tolling->station.obu.client_id) {
syslog_debug("[facilities] [tolling]<- received TPM.reply clientId different from ego");
return;
}
if (nonce != tolling->nonce) {
if (nonce != tolling->station.obu.nonce) {
syslog_err("[facilities] [tolling]<- received TPM.reply nonce different from sent request");
return;
}
@ -881,12 +899,12 @@ static void veh_handle_recv(tolling_t* tolling, TPM_t* tpm_rx, void* security_so
goto cleanup;
}
accepted = confirmation_code == TollConfirmationCode_accepted;
accepted = confirmation_code == TollingConfirmationCode_accepted;
}
syslog_info("[facilities] [tolling] received tolling payment reply | client:%lld nonce:%ld accepted:%s", (long long) tolling->client_id, tolling->nonce, accepted ? "yes" : "no");
syslog_info("[facilities] [tolling] received tolling payment reply | client:%lld nonce:%ld accepted:%s", (long long) tolling->station.obu.client_id, tolling->station.obu.nonce, accepted ? "yes" : "no");
tolling->active = false;
tolling->station.obu.active = false;
cleanup:
ASN_STRUCT_FREE(asn_DEF_SecurityRequest, sreq);
@ -894,53 +912,107 @@ cleanup:
}
int tpm_recv(void* fc, TPM_t* tpm_rx, void* security_socket, uint8_t* neighbour, uint8_t* src_addr) {
int rv = 0;
facilities_t* facilities = (facilities_t*) fc;
tolling_t* tolling = (tolling_t*) &facilities->tolling;
if (!tolling->enabled) {
syslog_debug("[facilities] [tolling] tolling is disabled");
goto cleanup;
return 0;
}
if (!tpm_rx->tpm.tollingType) {
if (!tpm_rx->tpm) {
syslog_err("[facilities] [tolling] received TPM does not have substructure TollingPaymentMessage");
return 1;
}
if (!tpm_rx->tpm->tollingType) {
syslog_err("[facilities] [tolling] received TPM does not have a type");
rv = 1;
goto cleanup;
return 1;
}
switch (tpm_rx->tpm.tollingType->present) {
case TollingType_PR_single:
switch (tpm_rx->tpm.tollingType->choice.single.present) {
case TollSingle_PR_request:
switch (tpm_rx->tpm->tollingType->present) {
// Entry
case TollingType_PR_entry:
switch (tpm_rx->tpm->tollingType->choice.entry.present) {
case TollingSingle_PR_request:
if (facilities->station_type != 15) {
syslog_debug("[facilities] [tolling] received TPM.request, ignoring");
goto cleanup;
syslog_debug("[facilities] [tolling] received TPM.entry.request, ignoring");
return 0;
}
rsu_handle_recv(facilities, tpm_rx, security_socket, neighbour, src_addr);
break;
case TollSingle_PR_reply:
case TollingSingle_PR_reply:
if (facilities->station_type == 15) {
syslog_debug("[facilities] [tolling] received TPM.reply, ignoring");
goto cleanup;
syslog_debug("[facilities] [tolling] received TPM.entry.reply, ignoring");
return 0;
}
pthread_mutex_lock(&facilities->epv.time.lock);
syslog_info("[facilities] [tolling] reply took %ld us", it2s_tender_get_now(TIME_MICROSECONDS) - tolling->tz);
syslog_info("[facilities] [tolling] entry.reply took %ld us", it2s_tender_get_now(TIME_MICROSECONDS) - tolling->tz);
pthread_mutex_unlock(&facilities->epv.time.lock);
veh_handle_recv(tolling, tpm_rx, security_socket, neighbour);
break;
default:
break;
}
break;
// Exit
case TollingType_PR_exit:
if (!tpm_rx->tpm->tollingType->choice.exit) {
return 1;
}
switch (tpm_rx->tpm->tollingType->choice.exit->present) {
case TollingSingle_PR_request:
if (facilities->station_type != 15) {
syslog_debug("[facilities] [tolling] received TPM.exit.request, ignoring");
return 0;
}
rsu_handle_recv(facilities, tpm_rx, security_socket, neighbour, src_addr);
break;
case TollingSingle_PR_reply:
if (facilities->station_type == 15) {
syslog_debug("[facilities] [tolling] received TPM.exit.reply, ignoring");
return 0;
}
pthread_mutex_lock(&facilities->epv.time.lock);
syslog_info("[facilities] [tolling] exit.reply took %ld us", it2s_tender_get_now(TIME_MICROSECONDS) - tolling->tz);
pthread_mutex_unlock(&facilities->epv.time.lock);
veh_handle_recv(tolling, tpm_rx, security_socket, neighbour);
break;
default:
break;
}
break;
case TollingType_PR_single:
switch (tpm_rx->tpm->tollingType->choice.single.present) {
case TollingSingle_PR_request:
if (facilities->station_type != 15) {
syslog_debug("[facilities] [tolling] received TPM.single.request, ignoring");
return 0;
}
rsu_handle_recv(facilities, tpm_rx, security_socket, neighbour, src_addr);
break;
case TollingSingle_PR_reply:
if (facilities->station_type == 15) {
syslog_debug("[facilities] [tolling] received TPM.single.reply, ignoring");
return 0;
}
pthread_mutex_lock(&facilities->epv.time.lock);
syslog_info("[facilities] [tolling] single.reply took %ld us", it2s_tender_get_now(TIME_MICROSECONDS) - tolling->tz);
pthread_mutex_unlock(&facilities->epv.time.lock);
veh_handle_recv(tolling, tpm_rx, security_socket, neighbour);
break;
default:
break;
}
break;
default:
break;
}
cleanup:
return rv;
return 0;
}
int tolling_init(tolling_t* tolling, void* zmq_ctx, char* security_address) {

View File

@ -26,12 +26,23 @@ typedef struct tolling {
bool enabled;
TOLLING_PROTOCOL_e protocol;
// Vehicles
bool active;
uint64_t nonce;
uint64_t client_id;
uint64_t tls_conn_id;
union {
// RSU
struct {
} rsu;
// OBU
struct {
bool active;
uint8_t toll_type;
uint64_t nonce;
uint64_t client_id;
uint64_t tls_conn_id;
TPM_t* entry_proof;
} obu;
} station;
uint64_t tz;
struct {