kopia lustrzana https://github.com/weetmuts/wmbusmeters
Add easy to access ordering of telegram content.
rodzic
4abc31d75c
commit
fc552d0566
|
@ -107,7 +107,8 @@ bool parseDV(Telegram *t,
|
|||
vector<uchar> &databytes,
|
||||
vector<uchar>::iterator data,
|
||||
size_t data_len,
|
||||
map<string,pair<int,DVEntry>> *values,
|
||||
map<string,pair<int,DVEntry>> *dv_entries,
|
||||
vector<DVEntry*> *dv_entries_ordered,
|
||||
vector<uchar>::iterator *format,
|
||||
size_t format_len,
|
||||
uint16_t *format_hash)
|
||||
|
@ -331,15 +332,17 @@ bool parseDV(Telegram *t,
|
|||
}
|
||||
string value = bin2hex(data, data_end, datalen);
|
||||
int offset = start_parse_here+data-data_start;
|
||||
(*values)[key] = { offset,
|
||||
DVEntry(key,
|
||||
mt,
|
||||
Vif(vif&0x7f),
|
||||
StorageNr(storage_nr),
|
||||
TariffNr(tariff),
|
||||
SubUnitNr(subunit),
|
||||
value)
|
||||
};
|
||||
|
||||
(*dv_entries)[key] = { offset, DVEntry(key,
|
||||
mt,
|
||||
Vif(vif&0x7f),
|
||||
StorageNr(storage_nr),
|
||||
TariffNr(tariff),
|
||||
SubUnitNr(subunit),
|
||||
value) };
|
||||
|
||||
(*dv_entries_ordered).push_back( &(*dv_entries)[key].second );
|
||||
|
||||
if (value.length() > 0) {
|
||||
// This call increments data with datalen.
|
||||
t->addExplanationAndIncrementPos(data, datalen, KindOfData::CONTENT, Understanding::NONE, "%s", value.c_str());
|
||||
|
@ -364,25 +367,25 @@ bool parseDV(Telegram *t,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool hasKey(std::map<std::string,std::pair<int,DVEntry>> *values, std::string key)
|
||||
bool hasKey(std::map<std::string,std::pair<int,DVEntry>> *dv_entries, std::string key)
|
||||
{
|
||||
return values->count(key) > 0;
|
||||
return dv_entries->count(key) > 0;
|
||||
}
|
||||
|
||||
bool findKey(MeasurementType mit, VIFRange vif, StorageNr storagenr, TariffNr tariffnr,
|
||||
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values)
|
||||
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *dv_entries)
|
||||
{
|
||||
return findKeyWithNr(mit, vif, storagenr, tariffnr, 1, key, values);
|
||||
return findKeyWithNr(mit, vif, storagenr, tariffnr, 1, key, dv_entries);
|
||||
}
|
||||
|
||||
bool findKeyWithNr(MeasurementType mit, VIFRange vif_range, StorageNr storagenr, TariffNr tariffnr, int nr,
|
||||
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *values)
|
||||
std::string *key, std::map<std::string,std::pair<int,DVEntry>> *dv_entries)
|
||||
{
|
||||
/*debug("(dvparser) looking for type=%s vif=%s storagenr=%d value_ran_low=%02x value_ran_hi=%02x\n",
|
||||
measurementTypeName(mit).c_str(), toString(vif), storagenr,
|
||||
low, hi);*/
|
||||
|
||||
for (auto& v : *values)
|
||||
for (auto& v : *dv_entries)
|
||||
{
|
||||
MeasurementType ty = v.second.second.measurement_type;
|
||||
Vif vi = v.second.second.vif;
|
||||
|
@ -430,12 +433,12 @@ void extractDV(string &s, uchar *dif, uchar *vif)
|
|||
*vif = bytes[i];
|
||||
}
|
||||
|
||||
bool extractDVuint8(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVuint8(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
uchar *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract uint8 from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
*value = 0;
|
||||
|
@ -444,7 +447,7 @@ bool extractDVuint8(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
vector<uchar> v;
|
||||
hex2bin(p.second.value, &v);
|
||||
|
@ -453,12 +456,12 @@ bool extractDVuint8(map<string,pair<int,DVEntry>> *values,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVuint16(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVuint16(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
uint16_t *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract uint16 from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
*value = 0;
|
||||
|
@ -467,7 +470,7 @@ bool extractDVuint16(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
vector<uchar> v;
|
||||
hex2bin(p.second.value, &v);
|
||||
|
@ -476,12 +479,12 @@ bool extractDVuint16(map<string,pair<int,DVEntry>> *values,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVuint24(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVuint24(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
uint32_t *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract uint24 from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
*value = 0;
|
||||
|
@ -490,7 +493,7 @@ bool extractDVuint24(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
vector<uchar> v;
|
||||
hex2bin(p.second.value, &v);
|
||||
|
@ -499,12 +502,12 @@ bool extractDVuint24(map<string,pair<int,DVEntry>> *values,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVuint32(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVuint32(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
uint32_t *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract uint32 from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
*value = 0;
|
||||
|
@ -513,7 +516,7 @@ bool extractDVuint32(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
vector<uchar> v;
|
||||
hex2bin(p.second.value, &v);
|
||||
|
@ -522,14 +525,14 @@ bool extractDVuint32(map<string,pair<int,DVEntry>> *values,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVdouble(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVdouble(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
double *value,
|
||||
bool auto_scale,
|
||||
bool assume_signed)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract double from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = 0;
|
||||
*value = 0;
|
||||
|
@ -538,7 +541,7 @@ bool extractDVdouble(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
|
||||
if (p.second.value.length() == 0) {
|
||||
|
@ -670,12 +673,12 @@ bool extractDVdouble(map<string,pair<int,DVEntry>> *values,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVlong(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVlong(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
uint64_t *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract long from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = 0;
|
||||
*value = 0;
|
||||
|
@ -684,7 +687,7 @@ bool extractDVlong(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
|
||||
if (p.second.value.length() == 0) {
|
||||
|
@ -789,17 +792,17 @@ bool extractDVlong(map<string,pair<int,DVEntry>> *values,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVHexString(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVHexString(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
string *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract string from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
return false;
|
||||
}
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
*value = p.second.value;
|
||||
|
||||
|
@ -807,12 +810,12 @@ bool extractDVHexString(map<string,pair<int,DVEntry>> *values,
|
|||
}
|
||||
|
||||
|
||||
bool extractDVReadableString(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVReadableString(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
string *value)
|
||||
{
|
||||
if ((*values).count(key) == 0) {
|
||||
if ((*dv_entries).count(key) == 0) {
|
||||
verbose("(dvparser) warning: cannot extract string from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
return false;
|
||||
|
@ -821,7 +824,7 @@ bool extractDVReadableString(map<string,pair<int,DVEntry>> *values,
|
|||
extractDV(key, &dif, &vif);
|
||||
int t = dif&0xf;
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
|
||||
string v = p.second.value;
|
||||
|
@ -888,12 +891,12 @@ bool extractTime(uchar hi, uchar lo, struct tm *date)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool extractDVdate(map<string,pair<int,DVEntry>> *values,
|
||||
bool extractDVdate(map<string,pair<int,DVEntry>> *dv_entries,
|
||||
string key,
|
||||
int *offset,
|
||||
struct tm *value)
|
||||
{
|
||||
if ((*values).count(key) == 0)
|
||||
if ((*dv_entries).count(key) == 0)
|
||||
{
|
||||
verbose("(dvparser) warning: cannot extract date from non-existant key \"%s\"\n", key.c_str());
|
||||
*offset = -1;
|
||||
|
@ -914,7 +917,7 @@ bool extractDVdate(map<string,pair<int,DVEntry>> *values,
|
|||
uchar dif, vif;
|
||||
extractDV(key, &dif, &vif);
|
||||
|
||||
pair<int,DVEntry>& p = (*values)[key];
|
||||
pair<int,DVEntry>& p = (*dv_entries)[key];
|
||||
*offset = p.first;
|
||||
vector<uchar> v;
|
||||
hex2bin(p.second.value, &v);
|
||||
|
@ -939,7 +942,7 @@ bool extractDVdate(map<string,pair<int,DVEntry>> *values,
|
|||
return ok;
|
||||
}
|
||||
|
||||
bool FieldMatcher::matches(int index, DVEntry &dv_entry)
|
||||
bool FieldMatcher::matches(DVEntry &dv_entry)
|
||||
{
|
||||
if (match_dif_vif_key)
|
||||
{
|
||||
|
|
|
@ -207,7 +207,7 @@ struct FieldMatcher
|
|||
|
||||
FieldMatcher &set(IndexNr i) { index_nr = i; return *this; }
|
||||
|
||||
bool matches(int index, DVEntry &dv_entry);
|
||||
bool matches(DVEntry &dv_entry);
|
||||
};
|
||||
|
||||
bool loadFormatBytesFromSignature(uint16_t format_signature, std::vector<uchar> *format_bytes);
|
||||
|
@ -218,7 +218,8 @@ bool parseDV(Telegram *t,
|
|||
std::vector<uchar> &databytes,
|
||||
std::vector<uchar>::iterator data,
|
||||
size_t data_len,
|
||||
std::map<std::string,std::pair<int,DVEntry>> *values,
|
||||
std::map<std::string,std::pair<int,DVEntry>> *dv_entries,
|
||||
std::vector<DVEntry*> *dv_entries_ordered,
|
||||
std::vector<uchar>::iterator *format = NULL,
|
||||
size_t format_len = 0,
|
||||
uint16_t *format_hash = NULL);
|
||||
|
|
|
@ -50,9 +50,11 @@ int main(int argc, char **argv)
|
|||
}
|
||||
}
|
||||
|
||||
map<string,pair<int,DVEntry>> values;
|
||||
vector<DVEntry> dv_entries_ordered;
|
||||
map<string,pair<int,DVEntry*>> dv_entries;
|
||||
|
||||
Telegram t;
|
||||
vector<uchar>::iterator i = databytes.begin();
|
||||
|
||||
parseDV(&t, databytes, i, databytes.size(), &values);
|
||||
parseDV(&t, databytes, i, databytes.size(), &dv_entries, &dv_entries_ordered);
|
||||
}
|
||||
|
|
|
@ -1504,7 +1504,15 @@ bool MeterCommonImplementation::handleTelegram(AboutTelegram &about, vector<ucha
|
|||
|
||||
void MeterCommonImplementation::processFieldExtractors(Telegram *t)
|
||||
{
|
||||
for (auto &fi : prints_)
|
||||
|
||||
/*
|
||||
for (auto i = t->dv_entries_ordered.begin(); i != t->dv_entries_ordered.end(); ++i)
|
||||
{
|
||||
printf("GURKA %s\n", (*i)->dif_vif_key.str().c_str());
|
||||
}
|
||||
*/
|
||||
|
||||
for (FieldInfo &fi : prints_)
|
||||
{
|
||||
fi.performExtraction(this, t);
|
||||
}
|
||||
|
|
|
@ -143,7 +143,8 @@ int test_crc()
|
|||
return rc;
|
||||
}
|
||||
|
||||
int test_parse(const char *data, std::map<std::string,std::pair<int,DVEntry>> *values, int testnr)
|
||||
int test_parse(const char *data, std::map<std::string,std::pair<int,DVEntry>> *dv_entries,
|
||||
std::vector<DVEntry*> *dv_entries_ordered, int testnr)
|
||||
{
|
||||
debug("\n\nTest nr %d......\n\n", testnr);
|
||||
bool b;
|
||||
|
@ -152,7 +153,7 @@ int test_parse(const char *data, std::map<std::string,std::pair<int,DVEntry>> *v
|
|||
hex2bin(data, &databytes);
|
||||
vector<uchar>::iterator i = databytes.begin();
|
||||
|
||||
b = parseDV(&t, databytes, i, databytes.size(), values);
|
||||
b = parseDV(&t, databytes, i, databytes.size(), dv_entries, dv_entries_ordered);
|
||||
|
||||
return b;
|
||||
}
|
||||
|
@ -203,30 +204,31 @@ void test_date(map<string,pair<int,DVEntry>> &values, const char *key, string da
|
|||
|
||||
int test_dvparser()
|
||||
{
|
||||
map<string,pair<int,DVEntry>> values;
|
||||
map<string,pair<int,DVEntry>> dv_entries;
|
||||
vector<DVEntry*> dv_entries_ordered;
|
||||
|
||||
int testnr = 1;
|
||||
test_parse("2F 2F 0B 13 56 34 12 8B 82 00 93 3E 67 45 23 0D FD 10 0A 30 31 32 33 34 35 36 37 38 39 0F 88 2F", &values, testnr);
|
||||
test_double(values, "0B13", 123.456, testnr);
|
||||
test_double(values, "8B8200933E", 234.567, testnr);
|
||||
test_string(values, "0DFD10", "30313233343536373839", testnr);
|
||||
test_parse("2F 2F 0B 13 56 34 12 8B 82 00 93 3E 67 45 23 0D FD 10 0A 30 31 32 33 34 35 36 37 38 39 0F 88 2F", &dv_entries, &dv_entries_ordered, testnr);
|
||||
test_double(dv_entries, "0B13", 123.456, testnr);
|
||||
test_double(dv_entries, "8B8200933E", 234.567, testnr);
|
||||
test_string(dv_entries, "0DFD10", "30313233343536373839", testnr);
|
||||
|
||||
testnr++;
|
||||
values.clear();
|
||||
test_parse("82046C 5f1C", &values, testnr);
|
||||
test_date(values, "82046C", "2010-12-31 00:00:00", testnr); // 2010-dec-31
|
||||
dv_entries.clear();
|
||||
test_parse("82046C 5f1C", &dv_entries, &dv_entries_ordered, testnr);
|
||||
test_date(dv_entries, "82046C", "2010-12-31 00:00:00", testnr); // 2010-dec-31
|
||||
|
||||
testnr++;
|
||||
values.clear();
|
||||
test_parse("0C1348550000426CE1F14C130000000082046C21298C0413330000008D04931E3A3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000046D0D0B5C2B03FD6C5E150082206C5C290BFD0F0200018C4079678885238310FD3100000082106C01018110FD610002FD66020002FD170000", &values, testnr);
|
||||
test_double(values, "0C13", 5.548, testnr);
|
||||
test_date(values, "426C", "2127-01-01 00:00:00", testnr); // 2127-jan-1
|
||||
test_date(values, "82106C", "2000-01-01 00:00:00", testnr); // 2000-jan-1
|
||||
dv_entries.clear();
|
||||
test_parse("0C1348550000426CE1F14C130000000082046C21298C0413330000008D04931E3A3CFE3300000033000000330000003300000033000000330000003300000033000000330000003300000033000000330000004300000034180000046D0D0B5C2B03FD6C5E150082206C5C290BFD0F0200018C4079678885238310FD3100000082106C01018110FD610002FD66020002FD170000", &dv_entries, &dv_entries_ordered, testnr);
|
||||
test_double(dv_entries, "0C13", 5.548, testnr);
|
||||
test_date(dv_entries, "426C", "2127-01-01 00:00:00", testnr); // 2127-jan-1
|
||||
test_date(dv_entries, "82106C", "2000-01-01 00:00:00", testnr); // 2000-jan-1
|
||||
|
||||
testnr++;
|
||||
values.clear();
|
||||
test_parse("426C FE04", &values, testnr);
|
||||
test_date(values, "426C", "2007-04-30 00:00:00", testnr); // 2010-dec-31
|
||||
dv_entries.clear();
|
||||
test_parse("426C FE04", &dv_entries, &dv_entries_ordered, testnr);
|
||||
test_date(dv_entries, "426C", "2007-04-30 00:00:00", testnr); // 2010-dec-31
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -1720,7 +1720,7 @@ bool Telegram::parse_TPL_72(vector<uchar>::iterator &pos)
|
|||
|
||||
if (decrypt_ok)
|
||||
{
|
||||
parseDV(this, frame, pos, remaining, &dv_entries);
|
||||
parseDV(this, frame, pos, remaining, &dv_entries, &dv_entries_ordered);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1735,7 +1735,7 @@ bool Telegram::parse_TPL_78(vector<uchar>::iterator &pos)
|
|||
header_size = distance(frame.begin(), pos);
|
||||
int remaining = distance(pos, frame.end());
|
||||
suffix_size = 0;
|
||||
parseDV(this, frame, pos, remaining, &dv_entries);
|
||||
parseDV(this, frame, pos, remaining, &dv_entries, &dv_entries_ordered);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1786,7 +1786,7 @@ bool Telegram::parse_TPL_79(vector<uchar>::iterator &pos)
|
|||
int remaining = distance(pos, frame.end());
|
||||
suffix_size = 0;
|
||||
|
||||
parseDV(this, frame, pos, remaining, &dv_entries, &format, format_bytes.size());
|
||||
parseDV(this, frame, pos, remaining, &dv_entries, &dv_entries_ordered, &format, format_bytes.size());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1804,7 +1804,7 @@ bool Telegram::parse_TPL_7A(vector<uchar>::iterator &pos)
|
|||
|
||||
if (decrypt_ok)
|
||||
{
|
||||
parseDV(this, frame, pos, remaining, &dv_entries);
|
||||
parseDV(this, frame, pos, remaining, &dv_entries, &dv_entries_ordered);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -506,8 +506,11 @@ struct Telegram
|
|||
void markAsSimulated() { is_simulated_ = true; }
|
||||
void markAsBeingAnalyzed() { being_analyzed_ = true; }
|
||||
|
||||
// Extracted mbus values.
|
||||
// The actual content of the (w)mbus telegram. The DifVif entries.
|
||||
// Mapped from their key for quick access to their offset and content.
|
||||
std::map<std::string,std::pair<int,DVEntry>> dv_entries;
|
||||
// And sorted in increasing offset order.
|
||||
std::vector<DVEntry*> dv_entries_ordered;
|
||||
|
||||
string autoDetectPossibleDrivers();
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue