Clean up some hash and RNG code
This commit is contained in:
parent
b6143a6125
commit
42f8862283
7 changed files with 213 additions and 83 deletions
|
@ -23,7 +23,7 @@ public:
|
|||
/**
|
||||
* Construct.
|
||||
*/
|
||||
request_hash(const std::string& _id, const uint8_t* _key, unsigned _nonce, skein_hash _h,
|
||||
request_hash(const std::string& _id, const uint8_t* _key, unsigned _nonce, skein::hash _h,
|
||||
const uint8_t* _prereq)
|
||||
: id(_id), nonce(_nonce), h(_h)
|
||||
{
|
||||
|
@ -46,7 +46,7 @@ public:
|
|||
uint8_t pubkey[32];
|
||||
uint8_t prereq[8];
|
||||
unsigned nonce;
|
||||
skein_hash h;
|
||||
skein::hash h;
|
||||
};
|
||||
/**
|
||||
* Create a new instance.
|
||||
|
|
|
@ -3,10 +3,22 @@
|
|||
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <stdexcept>
|
||||
|
||||
struct skein_hash
|
||||
namespace skein
|
||||
{
|
||||
/**
|
||||
* Skein hash function (v1.3).
|
||||
*/
|
||||
struct hash
|
||||
{
|
||||
/**
|
||||
* Variant to use (256-bit, 512-bit, 1024-bit)
|
||||
*/
|
||||
enum variant { PIPE_256, PIPE_512, PIPE_1024 };
|
||||
/**
|
||||
* Data type for piece of data.
|
||||
*/
|
||||
enum datatype
|
||||
{
|
||||
T_KEY = 0,
|
||||
|
@ -16,9 +28,39 @@ struct skein_hash
|
|||
T_NONCE = 20,
|
||||
T_MESSAGE = 48
|
||||
};
|
||||
skein_hash(variant v, uint64_t outbits);
|
||||
void write(const uint8_t* data, size_t datalen, datatype type = T_MESSAGE);
|
||||
void read(uint8_t* output);
|
||||
/**
|
||||
* Create a new hash state.
|
||||
*
|
||||
* Parameter v: The variant to use.
|
||||
* Parameter outbits: Number of output bits.
|
||||
* Throws std::runtime_error: Variant is invalid.
|
||||
*/
|
||||
hash(variant v, uint64_t outbits) throw(std::runtime_error);
|
||||
/**
|
||||
* Write data to be hashed.
|
||||
*
|
||||
* Parameter data: The data to append.
|
||||
* Parameter datalen: Number of bytes in data.
|
||||
* Parameter type: The data type. Must be monotonically increasing.
|
||||
* Throws std::runtime_error: Types not monotonic, or invalid type.
|
||||
*
|
||||
* Note: Data types 4 (CONFIG) and 63 (OUTPUT) are not allowed.
|
||||
*/
|
||||
void write(const uint8_t* data, size_t datalen, datatype type = T_MESSAGE) throw(std::runtime_error);
|
||||
/**
|
||||
* Read the output hash.
|
||||
*
|
||||
* Parameter output: Buffer to store the output to.
|
||||
*/
|
||||
void read(uint8_t* output) throw();
|
||||
/**
|
||||
* Read partial output hash.
|
||||
*
|
||||
* Parameter output: Buffer to store the output to.
|
||||
* Parameter startblock: The block number (each block is 256/512/1024 bits depending on variant) to start from.
|
||||
* Parameter bits: Number of bits to output.
|
||||
*/
|
||||
void read_partial(uint8_t* output, uint64_t startblock, uint64_t bits) throw();
|
||||
private:
|
||||
void typechange(uint8_t newtype);
|
||||
void configure();
|
||||
|
@ -34,5 +76,42 @@ private:
|
|||
int8_t last_type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Skein PRNG.
|
||||
*/
|
||||
struct prng
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* Construct a PRNG.
|
||||
*
|
||||
* Note: To seed the PRNG, write the initial seed there.
|
||||
*/
|
||||
prng() throw();
|
||||
/**
|
||||
* (Re)seed the PRNG and mark it seeded.
|
||||
*
|
||||
* Parameter buffer: Buffer to read the seed from.
|
||||
* Parameter size: Number of bytes in seed.
|
||||
*/
|
||||
void write(const void* buffer, size_t size) throw();
|
||||
/**
|
||||
* Read data from PRNG.
|
||||
*
|
||||
* Parameter buffer: Buffer to write the data to.
|
||||
* Parameter size: Number of random bytes to write.
|
||||
* Throws std::runtime_error: Generator is not seeded.
|
||||
*/
|
||||
void read(void* buffer, size_t size) throw(std::runtime_error);
|
||||
/**
|
||||
* Is seeded?
|
||||
*/
|
||||
bool is_seeded() const throw();
|
||||
private:
|
||||
uint8_t state[128];
|
||||
bool _is_seeded;
|
||||
};
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ namespace
|
|||
std::ifstream fp(path, std::ios::binary);
|
||||
if(!fp)
|
||||
throw std::runtime_error("Can't open dh25519 keyfile");
|
||||
skein_hash h(skein_hash::PIPE_512, 256);
|
||||
skein::hash h(skein::hash::PIPE_512, 256);
|
||||
while(true) {
|
||||
char buf[4096];
|
||||
fp.read(buf, sizeof(buf));
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include "library/loadlib.hpp"
|
||||
#include "library/sha256.hpp"
|
||||
#include "library/string.hpp"
|
||||
#include "library/skein.hpp"
|
||||
#include "library/serialization.hpp"
|
||||
#include "library/arch-detect.hpp"
|
||||
|
||||
|
@ -40,7 +41,7 @@
|
|||
|
||||
namespace
|
||||
{
|
||||
std::string rseed;
|
||||
skein::prng prng;
|
||||
uint64_t rcounter = 0;
|
||||
bool reached_main_flag;
|
||||
mutex_class seed_mutex;
|
||||
|
@ -85,41 +86,31 @@ namespace
|
|||
const int slots = 32;
|
||||
static unsigned count = 0;
|
||||
static uint64_t last_reseed = 0;
|
||||
static uint64_t buf[slots];
|
||||
static uint64_t buf[slots + 1];
|
||||
buf[count++] = arch_get_tsc();
|
||||
umutex_class h(seed_mutex);
|
||||
if(count == slots || buf[count - 1] - last_reseed > 300000000) {
|
||||
last_reseed = buf[count - 1];
|
||||
std::vector<char> x;
|
||||
x.resize(rseed.length() + slots * 8 + 8);
|
||||
std::copy(rseed.begin(), rseed.end(), x.begin());
|
||||
for(unsigned i = 0; i < slots; i++)
|
||||
serialization::u64l(&x[rseed.length() + 8 * i], buf[i]);
|
||||
serialization::u64l(&x[rseed.length() + 8 * slots], arch_get_random());
|
||||
rseed = "32 " + sha256::hash(reinterpret_cast<uint8_t*>(&x[0]), x.size());
|
||||
buf[slots] = arch_get_random();
|
||||
prng.write(buf, sizeof(buf));
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
std::string get_random_hexstring_64(size_t index)
|
||||
{
|
||||
std::ostringstream str;
|
||||
{
|
||||
umutex_class h(seed_mutex);
|
||||
str << rseed << " ";
|
||||
str << time(NULL) << " ";
|
||||
str << arch_get_tsc() << " ";
|
||||
str << arch_get_random() << " ";
|
||||
str << arch_get_random() << " ";
|
||||
str << arch_get_random() << " ";
|
||||
str << arch_get_random() << " ";
|
||||
str << (rcounter++) << " " << index;
|
||||
}
|
||||
std::string s = str.str();
|
||||
std::vector<char> x;
|
||||
x.resize(s.length());
|
||||
std::copy(s.begin(), s.end(), x.begin());
|
||||
return sha256::hash(reinterpret_cast<uint8_t*>(&x[0]), x.size());
|
||||
umutex_class h(seed_mutex);
|
||||
uint64_t buf[6];
|
||||
uint8_t out[32];
|
||||
buf[0] = time(NULL);
|
||||
buf[1] = arch_get_tsc();
|
||||
buf[2] = arch_get_random();
|
||||
buf[3] = arch_get_random();
|
||||
buf[4] = arch_get_random();
|
||||
buf[5] = arch_get_random();
|
||||
prng.write(buf, sizeof(buf));
|
||||
prng.read(out, sizeof(out));
|
||||
return hex::b_to(out, sizeof(out));
|
||||
}
|
||||
|
||||
std::string collect_identifying_information()
|
||||
|
@ -203,11 +194,10 @@ std::string get_random_hexstring(size_t length) throw(std::bad_alloc)
|
|||
|
||||
void set_random_seed(const std::string& seed) throw(std::bad_alloc)
|
||||
{
|
||||
std::ostringstream str;
|
||||
str << seed.length() << " " << seed;
|
||||
std::vector<char> x(seed.begin(), seed.end());
|
||||
{
|
||||
umutex_class h(seed_mutex);
|
||||
rseed = str.str();
|
||||
prng.write(&x[0], x.size());
|
||||
}
|
||||
rrdata.set_internal(random_rrdata());
|
||||
}
|
||||
|
@ -218,9 +208,9 @@ void set_random_seed() throw(std::bad_alloc)
|
|||
{
|
||||
std::ifstream r("/dev/urandom", std::ios::binary);
|
||||
if(r.is_open()) {
|
||||
char buf[64];
|
||||
r.read(buf, 64);
|
||||
std::string s(buf, 64);
|
||||
char buf[128];
|
||||
r.read(buf, sizeof(buf));
|
||||
std::string s(buf, sizeof(buf));
|
||||
set_random_seed(s);
|
||||
return;
|
||||
}
|
||||
|
@ -228,7 +218,7 @@ void set_random_seed() throw(std::bad_alloc)
|
|||
//If libgcrypt is available, use that.
|
||||
#ifdef USE_LIBGCRYPT_SHA256
|
||||
{
|
||||
char buf[64];
|
||||
char buf[128];
|
||||
gcry_randomize((unsigned char*)buf, sizeof(buf), GCRY_STRONG_RANDOM);
|
||||
std::string s(buf, sizeof(buf));
|
||||
set_random_seed(s);
|
||||
|
|
|
@ -262,18 +262,18 @@ dh25519_http_auth::request_hash dh25519_http_auth::start_request(const std::stri
|
|||
_nonce = nonce++;
|
||||
sprintf(buf, "%u", _nonce);
|
||||
|
||||
skein_hash hp(skein_hash::PIPE_512, 64);
|
||||
hp.write(ssecret, 32, skein_hash::T_KEY);
|
||||
hp.write((const uint8_t*)personalization.c_str(), personalization.length(), skein_hash::T_PERSONALIZATION);
|
||||
hp.write(pubkey, 32, skein_hash::T_PUBKEY);
|
||||
hp.write((uint8_t*)buf, strlen(buf), skein_hash::T_NONCE);
|
||||
skein::hash hp(skein::hash::PIPE_512, 64);
|
||||
hp.write(ssecret, 32, skein::hash::T_KEY);
|
||||
hp.write((const uint8_t*)personalization.c_str(), personalization.length(), skein::hash::T_PERSONALIZATION);
|
||||
hp.write(pubkey, 32, skein::hash::T_PUBKEY);
|
||||
hp.write((uint8_t*)buf, strlen(buf), skein::hash::T_NONCE);
|
||||
hp.read(prereq);
|
||||
|
||||
skein_hash h(skein_hash::PIPE_512, 256);
|
||||
h.write(ssecret, 32, skein_hash::T_KEY);
|
||||
h.write((const uint8_t*)personalization.c_str(), personalization.length(), skein_hash::T_PERSONALIZATION);
|
||||
h.write(pubkey, 32, skein_hash::T_PUBKEY);
|
||||
h.write((uint8_t*)buf, strlen(buf), skein_hash::T_NONCE);
|
||||
skein::hash h(skein::hash::PIPE_512, 256);
|
||||
h.write(ssecret, 32, skein::hash::T_KEY);
|
||||
h.write((const uint8_t*)personalization.c_str(), personalization.length(), skein::hash::T_PERSONALIZATION);
|
||||
h.write(pubkey, 32, skein::hash::T_PUBKEY);
|
||||
h.write((uint8_t*)buf, strlen(buf), skein::hash::T_NONCE);
|
||||
return request_hash(id, pubkey, _nonce, h, prereq);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,11 +4,14 @@
|
|||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
#include <iomanip>
|
||||
#include "skein512c.inc"
|
||||
#include "arch-detect.hpp"
|
||||
#include "hex.hpp"
|
||||
#include "minmax.hpp"
|
||||
|
||||
namespace skein
|
||||
{
|
||||
//Jerry Solinas was not here.
|
||||
#include "skein512c.inc"
|
||||
|
||||
static uint8_t bitmasks[] = {0, 128, 192, 224, 240, 248, 252, 254, 255};
|
||||
|
||||
|
@ -32,6 +35,28 @@ static void show_array(const char* prefix, const uint8_t* a, size_t e)
|
|||
std::cerr << std::endl;
|
||||
}
|
||||
|
||||
inline static void to_words(uint64_t* out, const void* in, size_t words)
|
||||
{
|
||||
#ifdef ARCH_IS_I386
|
||||
memcpy(out, in, words<<3);
|
||||
#else
|
||||
for(unsigned i = 0; i < words; i++)
|
||||
out[i]=0;
|
||||
for(unsigned i = 0; i < (words<<3); i++)
|
||||
out[i>>3]|=((uint64_t)reinterpret_cast<uint8_t*>(in)[i] << ((i&7)<<3));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline static void to_bytes(void* out, const uint64_t* in, size_t bytes)
|
||||
{
|
||||
#ifdef ARCH_IS_I386
|
||||
memcpy(out, in, bytes);
|
||||
#else
|
||||
for(size_t i = 0; i < bytes; i++)
|
||||
output[i] = (out[i>>3] >> ((i&7)<<3));
|
||||
#endif
|
||||
}
|
||||
|
||||
inline static void _skein256_compress(uint64_t* a, const uint64_t* b, const uint64_t* c, const uint64_t* d)
|
||||
{
|
||||
#ifdef TEST_SKEIN_CODE
|
||||
|
@ -71,7 +96,7 @@ inline static void _skein1024_compress(uint64_t* a, const uint64_t* b, const uin
|
|||
#endif
|
||||
}
|
||||
|
||||
skein_hash::skein_hash(skein_hash::variant v, uint64_t _outbits)
|
||||
hash::hash(hash::variant v, uint64_t _outbits) throw(std::runtime_error)
|
||||
{
|
||||
memset(chain, 0, sizeof(chain));
|
||||
memset(buffer, 0, sizeof(buffer));
|
||||
|
@ -88,7 +113,7 @@ skein_hash::skein_hash(skein_hash::variant v, uint64_t _outbits)
|
|||
last_type = -1;
|
||||
}
|
||||
|
||||
void skein_hash::configure()
|
||||
void hash::configure()
|
||||
{
|
||||
uint64_t config[16] = {0x133414853ULL,outbits};
|
||||
uint64_t tweak[2] = {32,0xC400000000000000ULL};
|
||||
|
@ -98,7 +123,7 @@ void skein_hash::configure()
|
|||
last_type = 4;
|
||||
}
|
||||
|
||||
void skein_hash::typechange(uint8_t newtype)
|
||||
void hash::typechange(uint8_t newtype)
|
||||
{
|
||||
if(last_type != newtype) {
|
||||
//Type change.
|
||||
|
@ -121,7 +146,7 @@ void skein_hash::typechange(uint8_t newtype)
|
|||
}
|
||||
}
|
||||
|
||||
void skein_hash::write(const uint8_t* data, size_t datalen, skein_hash::datatype type)
|
||||
void hash::write(const uint8_t* data, size_t datalen, hash::datatype type) throw(std::runtime_error)
|
||||
{
|
||||
if(type < 0 || type == 4 || type > 62)
|
||||
throw std::runtime_error("Invalid data type to write");
|
||||
|
@ -145,7 +170,7 @@ void skein_hash::write(const uint8_t* data, size_t datalen, skein_hash::datatype
|
|||
}
|
||||
}
|
||||
|
||||
void skein_hash::flush_buffer(uint8_t type, bool final)
|
||||
void hash::flush_buffer(uint8_t type, bool final)
|
||||
{
|
||||
uint64_t _buffer[16];
|
||||
uint64_t _buffer2[16];
|
||||
|
@ -159,14 +184,7 @@ void skein_hash::flush_buffer(uint8_t type, bool final)
|
|||
tweak[1] += (1ULL << 62);
|
||||
if(final)
|
||||
tweak[1] += (1ULL << 63);
|
||||
#ifdef ARCH_IS_I386
|
||||
memcpy(_buffer, buffer, fullbuffer);
|
||||
#else
|
||||
for(unsigned i = 0; i < (fullbuffer>>3); i++)
|
||||
_buffer[i]=0;
|
||||
for(unsigned i = 0; i < fullbuffer; i++)
|
||||
_buffer[i>>3]|=((uint64_t)buffer[i] << ((i&7)<<3));
|
||||
#endif
|
||||
to_words(_buffer, buffer, fullbuffer >> 3);
|
||||
compress(_buffer2, _buffer, chain, tweak);
|
||||
memcpy(chain, _buffer2, fullbuffer);
|
||||
data_low += bufferfill;
|
||||
|
@ -176,7 +194,7 @@ void skein_hash::flush_buffer(uint8_t type, bool final)
|
|||
memset(buffer, 0, fullbuffer);
|
||||
}
|
||||
|
||||
void skein_hash::read(uint8_t* output)
|
||||
void hash::read_partial(uint8_t* output, uint64_t startblock, uint64_t bits) throw()
|
||||
{
|
||||
typechange(63); //Switch to output.
|
||||
//The final one is special.
|
||||
|
@ -184,25 +202,68 @@ void skein_hash::read(uint8_t* output)
|
|||
uint64_t out[16];
|
||||
uint64_t tweak[2] = {8,0xFF00000000000000ULL};
|
||||
uint64_t offset = 0;
|
||||
for(uint64_t i = 0; i < outbits; i += (fullbuffer<<3)) {
|
||||
zeroes[0] = startblock;
|
||||
for(uint64_t i = 0; i < bits; i += (fullbuffer<<3)) {
|
||||
compress(out, zeroes, chain, tweak);
|
||||
zeroes[0]++;
|
||||
uint64_t fullbytes = (outbits - i) >> 3;
|
||||
if(fullbytes > fullbuffer) fullbytes = fullbuffer;
|
||||
#ifdef ARCH_IS_I386
|
||||
memcpy(output + offset, out, fullbytes);
|
||||
#else
|
||||
for(unsigned i = 0; i < fullbytes; i++)
|
||||
output[offset + i] = (out[i>>3] >> ((i&7)<<3));
|
||||
#endif
|
||||
if(fullbytes < fullbuffer && i + 8 * fullbytes < outbits) {
|
||||
uint64_t fullbytes = min((bits - i) >> 3, static_cast<uint64_t>(fullbuffer));
|
||||
to_bytes(output + offset, out, fullbytes);
|
||||
if(fullbytes < fullbuffer && i + 8 * fullbytes < bits) {
|
||||
output[offset + fullbytes] = (out[fullbytes>>3] >> ((fullbytes&7)<<3));
|
||||
output[offset + fullbytes] &= bitmasks[outbits&7];
|
||||
output[offset + fullbytes] &= bitmasks[bits&7];
|
||||
}
|
||||
offset += fullbuffer;
|
||||
}
|
||||
}
|
||||
|
||||
void hash::read(uint8_t* output) throw()
|
||||
{
|
||||
read_partial(output, 0, outbits);
|
||||
}
|
||||
|
||||
prng::prng() throw()
|
||||
{
|
||||
_is_seeded = false;
|
||||
memset(state, 0, 128);
|
||||
}
|
||||
|
||||
void prng::write(const void* buffer, size_t size) throw()
|
||||
{
|
||||
hash h(hash::PIPE_1024, 1024);
|
||||
h.write(state, 128, hash::T_NONCE);
|
||||
h.write(reinterpret_cast<const uint8_t*>(buffer), size, hash::T_MESSAGE);
|
||||
h.read(state);
|
||||
if(size > 0)
|
||||
_is_seeded = true;
|
||||
}
|
||||
|
||||
void prng::read(void* buffer, size_t size) throw(std::runtime_error)
|
||||
{
|
||||
if(!_is_seeded)
|
||||
throw std::runtime_error("PRNG is not initialized");
|
||||
//We can't use skein itself here, but the underlying compression function.
|
||||
uint64_t chain[16] = {0};
|
||||
uint64_t zeroes[16] = {0};
|
||||
uint64_t out[16];
|
||||
uint64_t tweak[2] = {8,0xFF00000000000000ULL};
|
||||
to_words(chain, state, 16);
|
||||
zeroes[0] = 1;
|
||||
for(uint64_t i = 0; i < size; i += 128) {
|
||||
_skein1024_compress(out, zeroes, chain, tweak);
|
||||
zeroes[0]++;
|
||||
uint64_t fullbytes = min(size - i, static_cast<uint64_t>(128));
|
||||
to_bytes(reinterpret_cast<uint8_t*>(buffer) + i, out, fullbytes);
|
||||
}
|
||||
zeroes[0] = 0;
|
||||
_skein1024_compress(out, zeroes, chain, tweak);
|
||||
to_bytes(state, out, 128);
|
||||
}
|
||||
|
||||
bool prng::is_seeded() const throw()
|
||||
{
|
||||
return _is_seeded;
|
||||
}
|
||||
}
|
||||
#ifdef TEST_SKEIN_CODE
|
||||
#define SKEIN_DEBUG
|
||||
#include <skein.h>
|
||||
|
@ -215,9 +276,9 @@ int main(int argc, char** argv)
|
|||
/*
|
||||
//skein_DebugFlag = SKEIN_DEBUG_STATE | SKEIN_DEBUG_TWEAK | SKEIN_DEBUG_INPUT_64;
|
||||
uint8_t out[128];
|
||||
skein_hash ctx(skein_hash::PIPE_512, 256);
|
||||
ctx.write((uint8_t*)argv[1], strlen(argv[1]), skein_hash::T_KEY);
|
||||
ctx.write((uint8_t*)argv[2], strlen(argv[2]), skein_hash::T_MESSAGE);
|
||||
skein::hash ctx(skein::hash::PIPE_512, 256);
|
||||
ctx.write((uint8_t*)argv[1], strlen(argv[1]), skein::hash::T_KEY);
|
||||
ctx.write((uint8_t*)argv[2], strlen(argv[2]), skein::hash::T_MESSAGE);
|
||||
ctx.read(out);
|
||||
show_array("New: ", out, 32);
|
||||
Skein_512_Ctxt_t ctx2;
|
||||
|
@ -235,9 +296,9 @@ int main()
|
|||
uint8_t buf[129] = {0xFF,0xFE,0xFD,0xFC,0xFB,0xFA,0xF9,0xF8,0xF7};
|
||||
uint8_t key[135] = {0x05,0x04,0x46,0x22,0x26,0x35,0x63,0x26,0xFF};
|
||||
uint8_t out[128];
|
||||
skein_hash ctx(skein_hash::PIPE_256, 256);
|
||||
ctx.write(key, sizeof(key), skein_hash::T_KEY);
|
||||
ctx.write(key, sizeof(key), skein_hash::T_NONCE);
|
||||
skein::hash ctx(skein::hash::PIPE_256, 256);
|
||||
ctx.write(key, sizeof(key), skein::hash::T_KEY);
|
||||
ctx.write(key, sizeof(key), skein::hash::T_NONCE);
|
||||
ctx.write(buf, 2);
|
||||
ctx.read(out);
|
||||
show_array("", out, 32);
|
||||
|
|
|
@ -172,7 +172,7 @@ void wxeditor_uploadtarget::generate_dh25519(wxCommandEvent& e)
|
|||
std::vector<char> x;
|
||||
x.resize(entropy.length());
|
||||
std::copy(entropy.begin(), entropy.end(), x.begin());
|
||||
skein_hash h(skein_hash::PIPE_1024, 1024);
|
||||
skein::hash h(skein::hash::PIPE_1024, 1024);
|
||||
h.write((uint8_t*)&x[0], x.size());
|
||||
h.read((uint8_t*)rbuf + 64);
|
||||
{
|
||||
|
|
Loading…
Add table
Reference in a new issue