Merge pull request #2593 from Akury83/master
[RRG-proxmark3.git] / armsrc / lfsampling.c
blob22fb6411aa819da833c4ad82e6213aaeff2a134e
1 //-----------------------------------------------------------------------------
2 // Copyright (C) Proxmark3 contributors. See AUTHORS.md for details.
3 //
4 // This program is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // This program is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // See LICENSE.txt for the text of the license.
15 //-----------------------------------------------------------------------------
16 // Miscellaneous routines for low frequency sampling.
17 //-----------------------------------------------------------------------------
19 #include "lfsampling.h"
21 #include "proxmark3_arm.h"
22 #include "BigBuf.h"
23 #include "fpgaloader.h"
24 #include "ticks.h"
25 #include "dbprint.h"
26 #include "util.h"
27 #include "lfdemod.h"
28 #include "string.h" // memset
29 #include "appmain.h" // print stack
30 #include "usb_cdc.h" // real-time sampling
33 Default LF config is set to:
34 decimation = 1 (we keep 1 out of 1 samples)
35 bits_per_sample = 8
36 averaging = YES
37 divisor = 95 (125kHz)
38 trigger_threshold = 0
39 samples_to_skip = 0
40 verbose = YES
43 static const sample_config def_config = {
44 .decimation = 1,
45 .bits_per_sample = 8,
46 .averaging = 1,
47 .divisor = LF_DIVISOR_125,
48 .trigger_threshold = 0,
49 .samples_to_skip = 0,
50 .verbose = false,
53 static sample_config config = { 1, 8, 1, LF_DIVISOR_125, 0, 0, true} ;
55 // Holds bit packed struct of samples.
56 static BitstreamOut_t data = {0, 0, 0};
58 // internal struct to keep track of samples gathered
59 static sampling_t samples = {0, 0, 0, 0};
61 void printLFConfig(void) {
62 uint32_t d = config.divisor;
63 DbpString(_CYAN_("LF Sampling config"));
64 Dbprintf(" [q] divisor............. %d ( "_GREEN_("%d.%02d kHz")" )", d, 12000 / (d + 1), ((1200000 + (d + 1) / 2) / (d + 1)) - ((12000 / (d + 1)) * 100));
65 Dbprintf(" [b] bits per sample..... %d", config.bits_per_sample);
66 Dbprintf(" [d] decimation.......... %d", config.decimation);
67 Dbprintf(" [a] averaging........... %s", (config.averaging) ? "yes" : "no");
68 Dbprintf(" [t] trigger threshold... %d", config.trigger_threshold);
69 Dbprintf(" [s] samples to skip..... %d ", config.samples_to_skip);
70 DbpString("");
73 void printSamples(void) {
74 DbpString(_CYAN_("LF Sampling memory usage"));
75 // Dbprintf(" decimation counter...%d", samples.dec_counter);
76 // Dbprintf(" sum..................%u", samples.sum);
77 Dbprintf(" counter.............. " _YELLOW_("%u"), samples.counter);
78 Dbprintf(" total saved.......... " _YELLOW_("%u"), samples.total_saved);
79 print_stack_usage();
83 void setDefaultSamplingConfig(void) {
84 setSamplingConfig(&def_config);
87 /**
88 * Called from the USB-handler to set the sampling configuration
89 * The sampling config is used for standard reading and sniffing.
91 * Other functions may read samples and ignore the sampling config,
92 * such as functions to read the UID from a prox tag or similar.
94 * Values set to '-1' implies no change
95 * @brief setSamplingConfig
96 * @param sc
98 void setSamplingConfig(const sample_config *sc) {
100 // decimation (1-8) how many bits of adc sample value to save
101 if (sc->decimation > 0 && sc->decimation < 9)
102 config.decimation = sc->decimation;
104 // bits per sample (1-8)
105 if (sc->bits_per_sample > 0 && sc->bits_per_sample < 9)
106 config.bits_per_sample = sc->bits_per_sample;
109 if (sc->averaging > -1)
110 config.averaging = (sc->averaging > 0) ? 1 : 0;
112 // Frequency divisor (19 - 255)
113 if (sc->divisor > 18 && sc->divisor < 256)
114 config.divisor = sc->divisor;
116 // Start saving samples when adc value larger than trigger_threshold
117 if (sc->trigger_threshold > -1)
118 config.trigger_threshold = sc->trigger_threshold;
120 // Skip n adc samples before saving
121 if (sc->samples_to_skip > -1)
122 config.samples_to_skip = sc->samples_to_skip;
124 if (sc->verbose)
125 printLFConfig();
128 sample_config *getSamplingConfig(void) {
129 return &config;
132 void initSampleBuffer(uint32_t *sample_size) {
133 initSampleBufferEx(sample_size, false);
136 void initSampleBufferEx(uint32_t *sample_size, bool use_malloc) {
138 if (sample_size == NULL) {
139 return;
142 BigBuf_free_keep_EM();
144 // We can't erase the buffer now, it would drastically delay the acquisition
145 if (use_malloc) {
147 if (*sample_size == 0) {
148 *sample_size = BigBuf_max_traceLen();
149 data.buffer = BigBuf_get_addr();
150 } else {
151 *sample_size = MIN(*sample_size, BigBuf_max_traceLen());
152 data.buffer = BigBuf_malloc(*sample_size);
155 } else {
156 if (*sample_size == 0) {
157 *sample_size = BigBuf_max_traceLen();
158 } else {
159 *sample_size = MIN(*sample_size, BigBuf_max_traceLen());
161 data.buffer = BigBuf_get_addr();
164 // reset data stream
165 data.numbits = 0;
166 data.position = 0;
168 // reset samples
169 samples.dec_counter = 0;
170 samples.sum = 0;
171 samples.counter = *sample_size;
172 samples.total_saved = 0;
175 uint32_t getSampleCounter(void) {
176 return samples.total_saved;
179 void logSampleSimple(uint8_t sample) {
180 logSample(sample, config.decimation, config.bits_per_sample, config.averaging);
183 void logSample(uint8_t sample, uint8_t decimation, uint8_t bits_per_sample, bool avg) {
185 if (!data.buffer) {
186 return;
189 // keep track of total gather samples regardless how many was discarded.
190 if (samples.counter-- == 0) {
191 return;
194 if (bits_per_sample == 0) {
195 bits_per_sample = 1;
198 if (bits_per_sample > 8) {
199 bits_per_sample = 8;
202 if (decimation == 0) {
203 decimation = 1;
206 if (avg) {
207 samples.sum += sample;
210 // check decimation
211 if (decimation > 1) {
212 samples.dec_counter++;
214 if (samples.dec_counter < decimation) {
215 return;
218 samples.dec_counter = 0;
221 // averaging
222 if (avg && decimation > 1) {
223 sample = samples.sum / decimation;
224 samples.sum = 0;
227 // store the sample
228 samples.total_saved++;
230 if (bits_per_sample == 8) {
232 data.buffer[samples.total_saved - 1] = sample;
234 // add number of bits.
235 data.numbits = samples.total_saved << 3;
237 } else {
238 // truncate trailing data
239 sample >>= 8 - bits_per_sample;
240 sample <<= 8 - bits_per_sample;
242 uint8_t bits_offset = data.numbits & 0x7;
243 uint8_t bits_cap = 8 - bits_offset;
245 // write the current byte
246 data.buffer[data.numbits >> 3] |= sample >> bits_offset;
247 uint32_t numbits = data.numbits + bits_cap;
249 // write the remaining bits to the next byte
250 data.buffer[numbits >> 3] |= sample << (bits_cap);
251 data.numbits += bits_per_sample;
256 * Setup the FPGA to listen for samples. This method downloads the FPGA bitstream
257 * if not already loaded, sets divisor and starts up the antenna.
258 * @param divisor : 1, 88> 255 or negative ==> 134.8 kHz
259 * 0 or 95 ==> 125 kHz
262 void LFSetupFPGAForADC(int divisor, bool reader_field) {
263 FpgaDownloadAndGo(FPGA_BITSTREAM_LF);
264 if ((divisor == 1) || (divisor < 0) || (divisor > 255)) {
265 FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_134); //~134kHz
266 } else if (divisor == 0) {
267 FpgaSendCommand(FPGA_CMD_SET_DIVISOR, LF_DIVISOR_125); //125kHz
268 } else {
269 FpgaSendCommand(FPGA_CMD_SET_DIVISOR, divisor);
272 FpgaWriteConfWord(FPGA_MAJOR_MODE_LF_READER | (reader_field ? FPGA_LF_ADC_READER_FIELD : 0));
274 // Connect the A/D to the peak-detected low-frequency path.
275 SetAdcMuxFor(GPIO_MUXSEL_LOPKD);
277 // Now set up the SSC to get the ADC samples that are now streaming at us.
278 FpgaSetupSsc(FPGA_MAJOR_MODE_LF_READER);
280 // start a 1.5ticks is 1us
281 StartTicks();
283 // 50ms for the resonant antenna to settle.
284 if (reader_field) {
285 WaitMS(50);
286 } else {
287 WaitMS(1);
292 * Does the sample acquisition. If threshold is specified, the actual sampling
293 * is not commenced until the threshold has been reached.
294 * This method implements decimation and quantization in order to
295 * be able to provide longer sample traces.
296 * Uses the following global settings:
297 * @param decimation - how much should the signal be decimated. A decimation of N means we keep 1 in N samples, etc.
298 * @param bits_per_sample - bits per sample. Max 8, min 1 bit per sample.
299 * @param averaging If set to true, decimation will use averaging, so that if e.g. decimation is 3, the sample
300 * value that will be used is the average value of the three samples.
301 * @param trigger_threshold - a threshold. The sampling won't commence until this threshold has been reached. Set
302 * to -1 to ignore threshold.
303 * @param verbose - is true, dbprints the status, else no outputs
304 * @return the number of bits occupied by the samples.
306 uint32_t DoAcquisition(uint8_t decimation, uint8_t bits_per_sample, bool avg, int16_t trigger_threshold,
307 bool verbose, uint32_t sample_size, uint32_t cancel_after, int32_t samples_to_skip, bool ledcontrol) {
309 initSampleBuffer(&sample_size); // sample size in bytes
310 sample_size <<= 3; // sample size in bits
311 sample_size /= bits_per_sample; // sample count
313 if (g_dbglevel >= DBG_DEBUG) {
314 printSamples();
317 bool trigger_hit = false;
318 uint32_t cancel_counter = 0;
319 int16_t checked = 0;
321 while (BUTTON_PRESS() == false) {
323 // only every 4000th times, in order to save time when collecting samples.
324 // interruptible only when logging not yet triggered
325 if (trigger_hit == false && (checked >= 4000)) {
326 if (data_available()) {
327 checked = -1;
328 break;
329 } else {
330 checked = 0;
333 ++checked;
335 WDT_HIT();
337 if (ledcontrol && (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY)) {
338 LED_D_ON();
341 if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
342 volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
344 // (RDV4) Test point 8 (TP8) can be used to trigger oscilloscope
345 if (ledcontrol) LED_D_OFF();
347 // threshold either high or low values 128 = center 0. if trigger = 178
348 if (trigger_hit == false) {
349 if ((trigger_threshold > 0) && (sample < (trigger_threshold + 128)) && (sample > (128 - trigger_threshold))) {
350 if (cancel_after > 0) {
351 cancel_counter++;
352 if (cancel_after == cancel_counter)
353 break;
355 continue;
357 trigger_hit = true;
360 if (samples_to_skip > 0) {
361 samples_to_skip--;
362 continue;
365 logSample(sample, decimation, bits_per_sample, avg);
367 if (samples.total_saved >= sample_size) break;
371 if (verbose) {
372 if (checked == -1) {
373 Dbprintf("lf sampling aborted");
374 } else if ((cancel_counter == cancel_after) && (cancel_after > 0)) {
375 Dbprintf("lf sampling cancelled after %u", cancel_counter);
378 Dbprintf("Done, saved " _YELLOW_("%d")" out of " _YELLOW_("%d")" seen samples at " _YELLOW_("%d")" bits/sample", samples.total_saved, samples.counter, bits_per_sample);
381 // Ensure that DC offset removal and noise check is performed for any device-side processing
382 if (bits_per_sample == 8) {
383 // these functions only consider bps==8
384 removeSignalOffset(data.buffer, samples.total_saved);
385 computeSignalProperties(data.buffer, samples.total_saved);
387 return data.numbits;
391 * @brief Does sample acquisition, ignoring the config values set in the sample_config.
392 * This method is typically used by tag-specific readers who just wants to read the samples
393 * the normal way
394 * @param trigger_threshold
395 * @param verbose
396 * @return number of bits sampled
398 uint32_t DoAcquisition_default(int trigger_threshold, bool verbose, bool ledcontrol) {
399 return DoAcquisition(1, 8, 0, trigger_threshold, verbose, 0, 0, 0, ledcontrol);
401 uint32_t DoAcquisition_config(bool verbose, uint32_t sample_size, bool ledcontrol) {
402 return DoAcquisition(config.decimation
403 , config.bits_per_sample
404 , config.averaging
405 , config.trigger_threshold
406 , verbose
407 , sample_size
408 , 0 // cancel_after
409 , config.samples_to_skip
410 , ledcontrol);
413 uint32_t DoPartialAcquisition(int trigger_threshold, bool verbose, uint32_t sample_size, uint32_t cancel_after, bool ledcontrol) {
414 return DoAcquisition(config.decimation
415 , config.bits_per_sample
416 , config.averaging
417 , trigger_threshold
418 , verbose
419 , sample_size
420 , cancel_after
422 , ledcontrol); // samples to skip
425 static uint32_t ReadLF(bool reader_field, bool verbose, uint32_t sample_size, bool ledcontrol) {
426 if (verbose)
427 printLFConfig();
429 LFSetupFPGAForADC(config.divisor, reader_field);
430 uint32_t ret = DoAcquisition_config(verbose, sample_size, ledcontrol);
431 StopTicks();
432 FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
433 return ret;
437 * Initializes the FPGA for reader-mode (field on), and acquires the samples.
438 * @return number of bits sampled
440 uint32_t SampleLF(bool verbose, uint32_t sample_size, bool ledcontrol) {
441 BigBuf_Clear_ext(false);
442 return ReadLF(true, verbose, sample_size, ledcontrol);
446 * Do LF sampling and send samples to the USB
448 * Uses parameters in config. Only bits_per_sample = 8 is working now
450 * @param reader_field - true for reading tags, false for sniffing
451 * @return sampling result
453 int ReadLF_realtime(bool reader_field) {
454 // parameters from config and constants
455 const uint8_t bits_per_sample = config.bits_per_sample;
456 const int16_t trigger_threshold = config.trigger_threshold;
457 int32_t samples_to_skip = config.samples_to_skip;
458 const uint8_t decimation = config.decimation;
460 const int8_t size_threshold_table[9] = {0, 64, 64, 60, 64, 60, 60, 56, 64};
461 const int8_t size_threshold = size_threshold_table[bits_per_sample];
463 // DoAcquisition() start
464 uint8_t last_byte = 0;
465 uint8_t curr_byte = 0;
466 int return_value = PM3_SUCCESS;
468 uint32_t sample_buffer_len = AT91C_USB_EP_IN_SIZE;
469 initSampleBuffer(&sample_buffer_len);
470 if (sample_buffer_len != AT91C_USB_EP_IN_SIZE) {
471 return PM3_EFAILED;
474 bool trigger_hit = false;
475 int16_t checked = 0;
477 return_value = async_usb_write_start();
478 if (return_value != PM3_SUCCESS) {
479 return return_value;
482 BigBuf_Clear_ext(false);
483 LFSetupFPGAForADC(config.divisor, reader_field);
485 while (BUTTON_PRESS() == false) {
486 // only every 4000th times, in order to save time when collecting samples.
487 // interruptible only when logging not yet triggered
488 if (trigger_hit == false && (checked >= 4000)) {
489 if (data_available()) {
490 checked = -1;
491 break;
492 } else {
493 checked = 0;
496 ++checked;
498 WDT_HIT();
500 if ((AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY)) {
501 LED_D_ON();
504 if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
505 volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
507 // (RDV4) Test point 8 (TP8) can be used to trigger oscilloscope
508 LED_D_OFF();
510 // threshold either high or low values 128 = center 0. if trigger = 178
511 if (trigger_hit == false) {
512 if ((trigger_threshold > 0) && (sample < (trigger_threshold + 128)) && (sample > (128 - trigger_threshold))) {
513 continue;
515 trigger_hit = true;
518 if (samples_to_skip > 0) {
519 samples_to_skip--;
520 continue;
523 logSample(sample, decimation, bits_per_sample, false);
525 // Write to USB FIFO if byte changed
526 curr_byte = data.numbits >> 3;
527 if (curr_byte > last_byte) {
528 async_usb_write_pushByte(data.buffer[last_byte]);
530 last_byte = curr_byte;
532 if (samples.total_saved == size_threshold) {
533 // Request USB transmission and change FIFO bank
534 if (async_usb_write_requestWrite() == false) {
535 return_value = PM3_EIO;
536 goto out;
539 // Reset sample
540 last_byte = 0;
541 data.numbits = 0;
542 samples.counter = size_threshold;
543 samples.total_saved = 0;
545 } else if (samples.total_saved == 1) {
546 // Check if there is any data from client
547 if (data_available_fast()) {
548 break;
554 return_value = async_usb_write_stop();
556 out:
557 LED_D_OFF();
559 // DoAcquisition() end
560 StopTicks();
561 FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF);
562 return return_value;
565 * Initializes the FPGA for sniffer-mode (field off), and acquires the samples.
566 * @return number of bits sampled
568 uint32_t SniffLF(bool verbose, uint32_t sample_size, bool ledcontrol) {
569 BigBuf_Clear_ext(false);
570 return ReadLF(false, verbose, sample_size, ledcontrol);
574 * acquisition of T55x7 LF signal. Similar to other LF, but adjusted with @marshmellows thresholds
575 * the data is collected in BigBuf.
577 void doT55x7Acquisition(size_t sample_size, bool ledcontrol) {
579 #define T55xx_READ_UPPER_THRESHOLD 128+60 // 60 grph
580 #define T55xx_READ_LOWER_THRESHOLD 128-60 // -60 grph
581 #define T55xx_READ_TOL 5
583 uint8_t *dest = BigBuf_get_addr();
584 uint16_t bufsize = BigBuf_max_traceLen();
586 if (bufsize > sample_size)
587 bufsize = sample_size;
589 uint8_t lastSample = 0;
590 uint16_t i = 0, skipCnt = 0;
591 bool startFound = false;
592 bool highFound = false;
593 bool lowFound = false;
595 uint16_t checker = 0;
597 if (g_dbglevel >= DBG_DEBUG) {
598 Dbprintf("doT55x7Acquisition - after init");
599 print_stack_usage();
602 while (skipCnt < 1000 && (i < bufsize)) {
604 if (BUTTON_PRESS())
605 break;
607 if (checker == 4000) {
608 if (data_available())
609 break;
610 else
611 checker = 0;
612 } else {
613 ++checker;
616 WDT_HIT();
618 if (ledcontrol && (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_TXRDY)) {
619 LED_D_ON();
622 if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
623 volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
624 if (ledcontrol) LED_D_OFF();
626 // skip until the first high sample above threshold
627 if (!startFound && sample > T55xx_READ_UPPER_THRESHOLD) {
628 highFound = true;
629 } else if (!highFound) {
630 skipCnt++;
631 continue;
633 // skip until the first low sample below threshold
634 if (!startFound && sample < T55xx_READ_LOWER_THRESHOLD) {
635 lastSample = sample;
636 lowFound = true;
637 } else if (!lowFound) {
638 skipCnt++;
639 continue;
642 // skip until first high samples begin to change
643 if (startFound || sample > T55xx_READ_LOWER_THRESHOLD + T55xx_READ_TOL) {
644 // if just found start - recover last sample
645 if (startFound == false) {
646 dest[i++] = lastSample;
647 startFound = true;
649 // collect samples
650 if (i < bufsize) {
651 dest[i++] = sample;
658 * acquisition of Cotag LF signal. Similart to other LF, since the Cotag has such long datarate RF/384
659 * and is Manchester?, we directly gather the manchester data into bigbuff
662 #define COTAG_T1 384
663 #define COTAG_T2 (COTAG_T1 >> 1)
664 #define COTAG_ONE_THRESHOLD 127+5
665 #define COTAG_ZERO_THRESHOLD 127-5
666 #ifndef COTAG_BITS
667 #define COTAG_BITS 264
668 #endif
669 void doCotagAcquisition(void) {
671 uint16_t bufsize = BigBuf_max_traceLen();
672 uint8_t *dest = BigBuf_malloc(bufsize);
674 dest[0] = 0;
676 bool firsthigh = false, firstlow = false;
677 uint16_t i = 0, noise_counter = 0;
679 uint16_t checker = 0;
681 while ((i < bufsize - 1) && (noise_counter < COTAG_T1 << 1)) {
683 if (BUTTON_PRESS())
684 break;
686 if (checker == 4000) {
687 if (data_available())
688 break;
689 else
690 checker = 0;
691 } else {
692 ++checker;
695 WDT_HIT();
697 if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
699 volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
701 // find first peak
702 if (firsthigh == false) {
703 if (sample < COTAG_ONE_THRESHOLD) {
704 noise_counter++;
705 continue;
708 noise_counter = 0;
709 firsthigh = true;
712 if (firstlow == false) {
713 if (sample > COTAG_ZERO_THRESHOLD) {
714 noise_counter++;
715 continue;
718 noise_counter = 0;
719 firstlow = true;
722 if (sample > COTAG_ONE_THRESHOLD) {
723 dest[i] = 255;
724 ++i;
725 } else if (sample < COTAG_ZERO_THRESHOLD) {
726 dest[i] = 0;
727 ++i;
728 } else {
729 dest[i] = dest[i - 1];
730 ++i;
735 // Ensure that DC offset removal and noise check is performed for any device-side processing
736 removeSignalOffset(dest, i);
737 computeSignalProperties(dest, i);
740 uint16_t doCotagAcquisitionManchester(uint8_t *dest, uint16_t destlen) {
742 if (dest == NULL)
743 return 0;
745 dest[0] = 0;
747 bool firsthigh = false, firstlow = false;
748 uint8_t curr = 0, prev = 0;
749 uint16_t i = 0;
750 uint16_t period = 0, checker = 0;
752 while ((i < destlen) && BUTTON_PRESS() == false) {
754 WDT_HIT();
756 if (checker == 4000) {
757 if (data_available())
758 break;
759 else
760 checker = 0;
761 } else {
762 ++checker;
766 if (AT91C_BASE_SSC->SSC_SR & AT91C_SSC_RXRDY) {
767 volatile uint8_t sample = (uint8_t)AT91C_BASE_SSC->SSC_RHR;
769 // find first peak
770 if (firsthigh == false) {
771 if (sample < COTAG_ONE_THRESHOLD) {
772 continue;
774 firsthigh = true;
777 if (firstlow == false) {
778 if (sample > COTAG_ZERO_THRESHOLD) {
779 continue;
781 firstlow = true;
784 // set sample 255, 0, or previous
785 if (sample > COTAG_ONE_THRESHOLD) {
786 prev = curr;
787 curr = 1;
788 } else if (sample < COTAG_ZERO_THRESHOLD) {
789 prev = curr;
790 curr = 0;
791 } else {
792 curr = prev;
795 // full T1 periods,
796 if (period > 0) {
797 --period;
798 continue;
801 dest[i] = curr;
802 ++i;
803 period = COTAG_T1;
807 return i;