From e018f30fdd17e2fbc7b8c2642dcd131faee4e0b1 Mon Sep 17 00:00:00 2001 From: Your Name Date: Sun, 28 Sep 2025 11:05:26 -0400 Subject: [PATCH] Version 0.0.1 --- README.md | 93 ++++++++++++++--- main.c | 237 +++++++++++++++++++++++++++++++++++++++---- test_combined.hex | 1 + test_size_suffix.hex | 2 + truerng | Bin 26592 -> 35160 bytes 5 files changed, 304 insertions(+), 29 deletions(-) create mode 100644 test_combined.hex create mode 100644 test_size_suffix.hex diff --git a/README.md b/README.md index 2767610..423658d 100644 --- a/README.md +++ b/README.md @@ -51,6 +51,9 @@ gcc -Wall -Wextra -std=c99 -O2 -o truerng main.c - `-f, --format ` - Output format: binary, hex, base64, decimal - Default: binary when piped, hex in interactive mode - `-o, --output ` - Output filename (works with all formats) +- `-d, --device ` - Select specific device (index, port, or type) + - Examples: `1`, `/dev/ttyACM0`, `pro`, `prov2`, `truerng` +- `-l, --list` - List all available TrueRNG devices - `-q, --quiet` - Suppress statistics/progress output - `-v, --verbose` - Show detailed device information - `-h, --help` - Show help message @@ -65,23 +68,51 @@ The `-n` option supports convenient size suffixes (case-insensitive): Decimal values are supported: `1.5MB`, `2.5GB`, etc. +### Device Selection + +When multiple TrueRNG devices are connected, you can select a specific device using the `-d` option: + +- **By index**: Use the device number from `--list` (e.g., `-d 1`, `-d 2`) +- **By port path**: Use the full device path (e.g., `-d /dev/ttyACM0`) +- **By device type**: Use device type keywords: + - `truerng` - Select original TrueRNG device + - `pro` - Select TrueRNGpro device + - `prov2` - Select TrueRNGproV2 device + +```bash +# List all available devices +./truerng --list + +# Use first device from list +./truerng -d 1 -n 1MB + +# Use specific port +./truerng -d /dev/ttyACM1 -n 512K + +# Use TrueRNGpro device +./truerng -d pro -n 2MB -f hex +``` + ### Examples ```bash # Basic usage - interactive mode with hex output ./truerng -# Generate 1KB of data in hex format +# List available devices +./truerng --list + +# Generate 1KB of data in hex format using first device ./truerng -n 1K -f hex -# Generate 2.5MB and pipe to another program -./truerng -n 2.5MB | xxd +# Generate 2.5MB using specific device and pipe to another program +./truerng -d 2 -n 2.5MB | xxd -# Save 1GB of binary data to file quietly -./truerng -n 1GB -o random.dat -q +# Save 1GB of binary data to file quietly using TrueRNGpro +./truerng -d pro -n 1GB -o random.dat -q -# Generate 512KB as hex and save to file -./truerng -n 512K -f hex -o output.hex +# Generate 512KB as hex and save to file using specific port +./truerng -d /dev/ttyACM0 -n 512K -f hex -o output.hex # Generate base64 output with verbose device info ./truerng -n 1MB -f base64 -v @@ -103,6 +134,14 @@ The program automatically detects the execution context: - **Interactive mode**: Shows progress, statistics, and defaults to hex output - **Piped mode**: Optimized for piping, defaults to binary output - **File output**: Can be combined with any format using `-o` option +- **Multiple devices**: Automatically detects and warns when multiple devices are present + +### Multiple Device Handling + +When multiple TrueRNG devices are connected: +- Without `-d` option: Uses the first available device with a warning +- With `-d` option: Uses the specified device +- Use `--list` to see all available devices and their details ## Configuration @@ -135,6 +174,14 @@ The C implementation: ## Sample Output +### Device Listing +```bash +./truerng --list +Available TrueRNG devices: + 1. TrueRNGproV2 at /dev/ttyACM0 (VID:04D8 PID:EBB5) + 2. TrueRNG at /dev/ttyACM2 (VID:04D8 PID:F5FE) +``` + ### Interactive Mode ``` TrueRNG - True Random Number Generator @@ -155,6 +202,17 @@ Extra zeros: 48 ======= ``` +### Multiple Device Warning +``` +Multiple TrueRNG devices found - using first available +(Use -d option to select specific device or --list to see all) +TrueRNG - True Random Number Generator +================================================== +TrueRNGproV2 Found +Using port: /dev/ttyACM0 +... +``` + ### Piped Mode with Verbose ``` TrueRNG - True Random Number Generator @@ -193,13 +251,24 @@ Options: Supports suffixes: K, MB, GB, TB (e.g., 1K, 2.5MB, 1GB) -f, --format Output format: binary, hex, base64, decimal (default: binary when piped, interactive otherwise) -o, --output Output filename (ignored in piped mode) + -d, --device Select specific device (index, port, or type) + Examples: 1, /dev/ttyACM0, pro, prov2 + -l, --list List all available TrueRNG devices -q, --quiet Suppress statistics/progress -v, --verbose Show detailed device information -h, --help Show this help message +Device Selection: + When multiple devices are present, use -d to select: + -d 1 Select first device from list + -d /dev/ttyACM1 Select by port path + -d pro Select TrueRNGpro device + -d prov2 Select TrueRNGproV2 device + -d truerng Select original TrueRNG device + Examples: - truerng -n 1024 -f hex # Interactive mode with hex output - truerng -n 1K -f hex # Same as above using K suffix - truerng -n 2.5MB | xxd # Piped mode with MB suffix - truerng -n 1GB -o random.dat -q # Save 1GB to file quietly - truerng -n 512K -f hex -o output.hex # Save 512KB as hex to file \ No newline at end of file + truerng --list # List available devices + truerng -n 1K -f hex # Use first available device + truerng -d 2 -n 1MB # Use second device from list + truerng -d /dev/ttyACM1 -n 512K # Use specific port + truerng -d pro -n 1GB -o random.dat # Use TrueRNGpro device \ No newline at end of file diff --git a/main.c b/main.c index 08b7a41..e67e9f7 100644 --- a/main.c +++ b/main.c @@ -48,11 +48,25 @@ // Output formats enum Format { BINARY, HEX, BASE64, DECIMAL }; +// Device types +enum DeviceType { DEVICE_TRUERNGPROV2 = 1, DEVICE_TRUERNGPRO = 2, DEVICE_TRUERNG = 3 }; + +// Device information structure +struct DeviceInfo { + char port_path[MAX_PATH]; + char device_name[32]; + enum DeviceType type; + char vid[8]; + char pid[8]; +}; + // Command-line options structure struct Options { long long bytes; enum Format format; char *output_file; + char *device_selector; + int list_devices; int quiet; int verbose; int help; @@ -183,23 +197,35 @@ void print_usage(void) { printf(" Supports suffixes: K, MB, GB, TB (e.g., 1K, 2.5MB, 1GB)\n"); printf(" -f, --format Output format: binary, hex, base64, decimal (default: binary when piped, interactive otherwise)\n"); printf(" -o, --output Output filename (ignored in piped mode)\n"); + printf(" -d, --device Select specific device (index, port, or type)\n"); + printf(" Examples: 1, /dev/ttyACM0, pro, prov2\n"); + printf(" -l, --list List all available TrueRNG devices\n"); printf(" -q, --quiet Suppress statistics/progress\n"); printf(" -v, --verbose Show detailed device information\n"); printf(" -h, --help Show this help message\n"); + printf("\nDevice Selection:\n"); + printf(" When multiple devices are present, use -d to select:\n"); + printf(" -d 1 Select first device from list\n"); + printf(" -d /dev/ttyACM1 Select by port path\n"); + printf(" -d pro Select TrueRNGpro device\n"); + printf(" -d prov2 Select TrueRNGproV2 device\n"); + printf(" -d truerng Select original TrueRNG device\n"); printf("\nExamples:\n"); - printf(" truerng -n 1024 -f hex # Interactive mode with hex output\n"); - printf(" truerng -n 1K -f hex # Same as above using K suffix\n"); - printf(" truerng -n 2.5MB | xxd # Piped mode with MB suffix\n"); - printf(" truerng -n 1GB -o random.dat -q # Save 1GB to file quietly\n"); - printf(" truerng -n 512K -f hex -o output.hex # Save 512KB as hex to file\n"); + printf(" truerng --list # List available devices\n"); + printf(" truerng -n 1K -f hex # Use first available device\n"); + printf(" truerng -d 2 -n 1MB # Use second device from list\n"); + printf(" truerng -d /dev/ttyACM1 -n 512K # Use specific port\n"); + printf(" truerng -d pro -n 1GB -o random.dat # Use TrueRNGpro device\n"); } int parse_arguments(int argc, char *argv[]) { - static const char *opt_string = "n:f:o:qvh"; + static const char *opt_string = "n:f:o:d:lqvh"; static struct option long_options[] = { {"bytes", required_argument, 0, 'n'}, {"format", required_argument, 0, 'f'}, {"output", required_argument, 0, 'o'}, + {"device", required_argument, 0, 'd'}, + {"list", no_argument, 0, 'l'}, {"quiet", no_argument, 0, 'q'}, {"verbose", no_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, @@ -209,6 +235,8 @@ int parse_arguments(int argc, char *argv[]) { options.bytes = 1048576; // Default 1MB options.format = BINARY; options.output_file = NULL; + options.device_selector = NULL; + options.list_devices = 0; options.quiet = 0; options.verbose = 0; options.help = 0; @@ -239,6 +267,12 @@ int parse_arguments(int argc, char *argv[]) { case 'o': options.output_file = optarg; break; + case 'd': + options.device_selector = optarg; + break; + case 'l': + options.list_devices = 1; + break; case 'q': options.quiet = 1; break; @@ -262,7 +296,9 @@ int is_piped(void) { // Function prototypes void init_ones_lookup_table(void); -int find_truerng_port(char* port_path, int piped, int verbose); +int find_all_truerng_devices(struct DeviceInfo devices[], int max_devices); +int select_device_from_list(struct DeviceInfo devices[], int device_count, const char* selector, char* selected_port); +void list_devices(struct DeviceInfo devices[], int device_count); int setup_serial_port(const char* port_path); int read_usb_device_info(const char* port_name, char* vid, char* pid); double get_time_diff(struct timeval start, struct timeval end); @@ -289,6 +325,132 @@ void init_ones_lookup_table(void) { } } +// Find all TrueRNG devices and populate device list +// Returns: number of devices found +int find_all_truerng_devices(struct DeviceInfo devices[], int max_devices) { + DIR *dir; + struct dirent *entry; + char vid[8], pid[8]; + int device_count = 0; + + dir = opendir("/dev"); + if (dir == NULL) { + perror("Cannot open /dev directory"); + return 0; + } + + while ((entry = readdir(dir)) != NULL && device_count < max_devices) { + // Look for ttyUSB* or ttyACM* devices + if (strncmp(entry->d_name, "ttyUSB", 6) == 0 || + strncmp(entry->d_name, "ttyACM", 6) == 0) { + + if (read_usb_device_info(entry->d_name, vid, pid)) { + // Convert to uppercase for comparison + for (int i = 0; vid[i]; i++) vid[i] = toupper(vid[i]); + for (int i = 0; pid[i]; i++) pid[i] = toupper(pid[i]); + + // Check for TrueRNGproV2 + if (strcmp(vid, TRUERNGPROV2_PID) == 0 && strcmp(pid, TRUERNGPROV2_HID) == 0) { + snprintf(devices[device_count].port_path, MAX_PATH, "/dev/%s", entry->d_name); + strcpy(devices[device_count].device_name, "TrueRNGproV2"); + devices[device_count].type = DEVICE_TRUERNGPROV2; + strcpy(devices[device_count].vid, vid); + strcpy(devices[device_count].pid, pid); + device_count++; + continue; + } + + // Check for TrueRNGpro + if (strcmp(vid, TRUERNGPRO_PID) == 0 && strcmp(pid, TRUERNGPRO_HID) == 0) { + snprintf(devices[device_count].port_path, MAX_PATH, "/dev/%s", entry->d_name); + strcpy(devices[device_count].device_name, "TrueRNGpro"); + devices[device_count].type = DEVICE_TRUERNGPRO; + strcpy(devices[device_count].vid, vid); + strcpy(devices[device_count].pid, pid); + device_count++; + continue; + } + + // Check for TrueRNG + if (strcmp(vid, TRUERNG_PID) == 0 && strcmp(pid, TRUERNG_HID) == 0) { + snprintf(devices[device_count].port_path, MAX_PATH, "/dev/%s", entry->d_name); + strcpy(devices[device_count].device_name, "TrueRNG"); + devices[device_count].type = DEVICE_TRUERNG; + strcpy(devices[device_count].vid, vid); + strcpy(devices[device_count].pid, pid); + device_count++; + continue; + } + } + } + } + + closedir(dir); + return device_count; +} + +// List all available devices +void list_devices(struct DeviceInfo devices[], int device_count) { + if (device_count == 0) { + printf("No TrueRNG devices found.\n"); + return; + } + + printf("Available TrueRNG devices:\n"); + for (int i = 0; i < device_count; i++) { + printf(" %d. %s at %s (VID:%s PID:%s)\n", + i + 1, + devices[i].device_name, + devices[i].port_path, + devices[i].vid, + devices[i].pid); + } +} + +// Select device from list based on selector string +// Returns: 0 on success, -1 on error +int select_device_from_list(struct DeviceInfo devices[], int device_count, const char* selector, char* selected_port) { + if (device_count == 0) { + return -1; + } + + // If no selector, use first device + if (!selector) { + strcpy(selected_port, devices[0].port_path); + return 0; + } + + // Try to parse as device index (1-based) + char* endptr; + long index = strtol(selector, &endptr, 10); + if (*endptr == '\0' && index >= 1 && index <= device_count) { + strcpy(selected_port, devices[index - 1].port_path); + return 0; + } + + // Try to match by port path + if (strncmp(selector, "/dev/", 5) == 0) { + for (int i = 0; i < device_count; i++) { + if (strcmp(devices[i].port_path, selector) == 0) { + strcpy(selected_port, devices[i].port_path); + return 0; + } + } + } + + // Try to match by device type + for (int i = 0; i < device_count; i++) { + if ((strcasecmp(selector, "prov2") == 0 && devices[i].type == DEVICE_TRUERNGPROV2) || + (strcasecmp(selector, "pro") == 0 && devices[i].type == DEVICE_TRUERNGPRO) || + (strcasecmp(selector, "truerng") == 0 && devices[i].type == DEVICE_TRUERNG)) { + strcpy(selected_port, devices[i].port_path); + return 0; + } + } + + return -1; // No match found +} + // Read USB device info from sysfs int read_usb_device_info(const char* port_name, char* vid, char* pid) { char path[MAX_PATH]; @@ -471,6 +633,33 @@ int main(int argc, char *argv[]) { return 0; } + // Initialize lookup table + init_ones_lookup_table(); + + // Find all available devices + struct DeviceInfo devices[MAX_PORTS]; + int device_count = find_all_truerng_devices(devices, MAX_PORTS); + + // Handle device listing + if (options.list_devices) { + list_devices(devices, device_count); + return 0; + } + + // Check if any devices were found + if (device_count == 0) { + fprintf(stderr, "No TrueRNG devices found\n"); + return 1; + } + + // Select device based on options + char port_path[MAX_PATH]; + if (select_device_from_list(devices, device_count, options.device_selector, port_path) != 0) { + fprintf(stderr, "Error: Could not select device '%s'\n", options.device_selector); + fprintf(stderr, "Use --list to see available devices\n"); + return 1; + } + // Determine mode: interactive (no args) vs piped (has args or actual piping) int interactive_mode = (argc == 1); int piped_mode = !interactive_mode || is_piped(); @@ -481,6 +670,16 @@ int main(int argc, char *argv[]) { options.format = HEX; } + // Show multiple device warning if verbose or interactive + if (device_count > 1 && !options.quiet) { + if (interactive_mode || (piped_mode && options.verbose)) { + if (!options.device_selector) { + printf("Multiple TrueRNG devices found - using first available\n"); + printf("(Use -d option to select specific device or --list to see all)\n"); + } + } + } + // Print headers based on mode if (interactive_mode && !options.quiet) { // Interactive mode: always show headers unless quiet @@ -493,7 +692,20 @@ int main(int argc, char *argv[]) { printf("==================================================\n"); } - char port_path[MAX_PATH]; + // Find the selected device info for display + const char* device_name = "Unknown"; + for (int i = 0; i < device_count; i++) { + if (strcmp(devices[i].port_path, port_path) == 0) { + device_name = devices[i].device_name; + break; + } + } + + // Show device info + if ((interactive_mode && !options.quiet) || (piped_mode && options.verbose && !options.quiet)) { + printf("%s Found\n", device_name); + } + int serial_fd; FILE *fp; unsigned char buffer[BLOCKSIZE]; @@ -503,15 +715,6 @@ int main(int argc, char *argv[]) { struct timeval starttime, currenttime; double elapsed_time, rate; - // Initialize lookup table - init_ones_lookup_table(); - - // Find TrueRNG device - if (!find_truerng_port(port_path, piped_mode, options.verbose)) { - fprintf(stderr, "TrueRNG Not Found\n"); - return 1; - } - // Setup output if (piped_mode && !options.output_file) { fp = stdout; diff --git a/test_combined.hex b/test_combined.hex new file mode 100644 index 0000000..33cb5a8 --- /dev/null +++ b/test_combined.hex @@ -0,0 +1 @@ +b840d95473b9c6b70441dcfb8f99752e3d0873c70955cdaec370775d4ebdec4f447e2a5024b184dae5a691b3674d3d79a79c85032d4eb04f42e1e7c55b724cb43c813ced3fd6c44068a8d33dc39a66fc0c142e6720338e34b22334b7fa562efd3421b12f diff --git a/test_size_suffix.hex b/test_size_suffix.hex new file mode 100644 index 0000000..b80f1df --- /dev/null +++ b/test_size_suffix.hex @@ -0,0 +1,2 @@ +b5ad1a2e70f717484d7ea8a9ed773b616e9c01f426a142b8a9558365b673d6f7be913381320ccdd2944e857234dd12220a5c4672928c13fb77972b0ee02892d4f7984d94a23196d5aab92a9d7d6208387b828a47a045496476e34b54cbc0171374435d99489118a02c6435b1df6dee752014043cdfb9b36ced09404b1534ff1a4eb1e81c1b8dc36c96b311c206639dba2fc87596431494058d8a54f0b9280980cd8f60db61e9d4ccfed2348d92b91f7fa6e18b0bc3a6794bbd852c6d22eb51f1df7716450eac298eb6209db8ae39dc8ac57906ef42726fe2bcf2edbab9fc5c3e5ada2b53ec5b6ebaeaf7c113d851273fe5b3e5c4d9c492ec39af77f2876a258081a6d482bd70c5075916719c08cb76cace9a4d70be94de54e2ae9a49332acfb4eafe6257be2b583673e195041f9bdf6e3b1042e7c25882fc30fd4466e5b1f93c4d828bb3f74e4ece86948e65facfe233eaa2d52caae02d8824f0f18655e5723973b8b8db37bcde508d18ce91bdf9325418a53aa079e7a0b46752364b10f234281db5931464ad53d0f4f38d44c4108742a87ce570b6bc2703d7bd79b950290ebcb638f49bea08996e0bd9ca2c183c84fb29be0b2ee9d6b5ad61fe3c3e0765c01ea8c8ef93e20f55c2e845a9b96b40b1c5004a077c18d29d92287bdd07e9de08b5beb701dc07dc7b8b05838012aef8f7d34f6618d74e382cde984016784bb1ca97e7c825023d0ea222a7375e22fd0b3f415c6e3cf16559a980b051b2bd716a0cefd11e4ecefa6a8c71c50a08f262f2c875d4ba989703a3c8f4068f8ede04768e932514b1efbaeca5d32c291f2d90142a28e0b73c6f9bcbb8dc4cd45574e04116d21ee49a6f3b42ffe22ef34d14d99f85fe69d97023d8bb75e7ad443ac430988ccd26d69e511906cba9b935c66df1e1643b4c53295edb1d07c12c50c28b9580d84f837898ea2bb9d815cc7f0ad41471a7e0a01893cc1220c2a77e291073256ab318aa07428b907a9179eb6ec6bbe2912064b9502116b840a6e3413447b1ed2d4301e8422da451b9e39bc3ae0eddd821b523e0b42374754998ff9cd6f4d50535c22586374f332ffde03e6f926a6b2c6d10751fba86cd43f2bb4a9aea5af15b7e7bf51da92f2162ee0adee6f55b19f6d0230c7f15674f435b133fb52423dca9101e5c3994d9f547542bbbe35c49a6d658cac36ac5e731bdd27af3daf09884fd8006c961ff3e56fcbbc2d5d2f44b86eab7bd67d8dcdc10e397fc7032cd499282b6b0699277e92d553aecbca2bd39159af4a36d9e64a8934acbfaf532e1f1e4d338ab46296d0501cb1966fddbe845143dbc5a242adc1c14b258259e349bdce70b939f4dd6bc6d5923a0a9a305765d13a0b65181af280ee847ad6e5e2f4c08e63e775bfc45a09e0c08e9f97c0cb05403b3bea181f78989cc0f8b815ce86d651d68aace01 +aa0501e41fead7cb6474caf23187fa5050d0ba5452d6056d20c462eed32dff6002816490d6a2b3983e224ae6942ad00ac01c1f2a0905eee0cd748f11e17c7ec53d6c486b2f0ae1dab65a1d006e4ff3baaf422e330a05273dbf4c933141ab85dc726e5e60b8fd452a4310f0972e7b9a8fd027112db21f35f220edd18a6e469454bab230dbb0543f8220c2b96cbe1ab2e14865a9ad7347d42e203870e30166a1377350888f2f146374bb5386cbcacb8753aff7c66f39ed93d221ac6a7037ec91db588e860450d07fc0c09ee0e37ffdc8312ff262b214833ce7183e2dabb8667c597fd38652931d69fc99ff6149e854f44ace4f06bea49335d9c2254b7b2a11d8f486d542330bb378a113a3a9856b115549ac7bcfc8513dfd6c0ab2a6d6931e56c8df982a5a10f80481ca02639d2bddcb084cc1ee62f8ecbf1c00f660d56a8fc4564d8992f33d6b14f1bd4bb85b35cee96da6e12ecf9d979a632f34e8effc70ae0b211f00a6f5d4c534275567a24256d68c3f56f4a5b06b2d9357e6b86ecfc30bc3e9f62ee97bb9540376bf87eaf63a92539c217dc9ed700fbf0c258ed28b40a4f6ec184c34114947a9cfdd360a209f56d22adb9ef413fc09317f77c51703ca945e8e1ffb1833aeb2516ad4e5ad0990fe148da244dfd4cb1b5c4ea940cdd8e80c1e58d43ad5999aa1f067564428e083350ae3ed170b674bfec379cb59f8bc5e7ace524ab8c9809d6e746c6e2062dc854010d7d0e01aa6cba4e229bf9a4fce37b3ddddd38e12722e24e29b806fdde1b701edbbbe6270623aadb1c22ec67fc69a84225fc4f5b8885e8895b1717c8a232fe773333561513d429584770ea86728b3eec3ee780f08a8cc9357cb6da4a007bbba13f556f0efae6e408731bec18852c4b91568358bccaa5ed061b607e4d71d484185b0dc5ca114ff5ce4dd4b42c78436bba590614e82445dd10ed8ada9689e90afbeea6a84aac8a7c7673b8369b6d4536d6f0b715f32aa686ff6f6c4b02d4e4096954a10b686cdfc0233f68f080fb75dc4fe1679143dae10e466fc57af144944419c8b629a01510bff0cb6bd048849f78a43e024cc7596895ef59d36991adb5869a42f4fc4b9557984b8dc373b7e27b1175a16686dfef79d29e3e62e324bfdf797f8a0f8965cde8d18d8e1d62c945c0b98de92aa53e3b6e0eea212498840b2e3659c3d8652f4543f6e814df2cba04c32d524444b5cbc50be7849b324d45ce2fe9db8622f7c2f9117acd27fd7a63828657d313d93d9f0f21e57810957c1f34994eb32ee7a532b4f20f221b26247043bd4175b825f8014728c0a9f9ea799f101ab30a45d50ed977e8ce571cad37c5998f47e64964a41274d0fb202b8945eea54863bfcd1baed363921375687dc0ac83c5c157a0a2788f7fe204409292f6ee4e2ebeeb8b2ff62b332c12f018cc81fe4262198d2 diff --git a/truerng b/truerng index 12a9e2bed34da8f1a6642a53fab769b091a09280..8be6cf5dbaac51e2bad0084949fffad5b42e6cb4 100755 GIT binary patch literal 35160 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 B;SB%) literal 26592 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 F^k0CIlMw&_