Fix 15 snoop (#752)

* fixing hf 15: implement hf 15 snoop
* rename hf 15 record to hf 15 snoop
* speedup sampling / decoding:
*   new FPGA mode FPGA_HF_READER_RX_XCORR_AMPLITUDE implements amplitude(ci, cq) on FPGA
*   inlining the decoders in iso15693.c
*   inlining memcpy/memset in LogTrace()
*   giving up the moving correlator for SOF in Handle15693SamplesFromTag
* decode more of EOF in Handle15693SamplesFromTag()
* some refactoring
This commit is contained in:
pwpiwi 2019-01-12 13:28:26 +01:00 committed by GitHub
commit d9de20fa4b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 715 additions and 574 deletions

View file

@ -13,6 +13,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
### Fixed ### Fixed
- AC-Mode decoding for HitagS - AC-Mode decoding for HitagS
- Wrong UID at HitagS simulation - Wrong UID at HitagS simulation
- 'hf 15 sim' now works as expected (piwi)
### Added ### Added
- Support Standard Communication Mode in HITAG S - Support Standard Communication Mode in HITAG S
@ -24,6 +25,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac
- Added `hf fido` `assert` and `make` commands from fido2 protocol (authenticatorMakeCredential and authenticatorGetAssertion) (Merlok) - Added `hf fido` `assert` and `make` commands from fido2 protocol (authenticatorMakeCredential and authenticatorGetAssertion) (Merlok)
- Added `lf paradox clone` to clone a Paradox card - Added `lf paradox clone` to clone a Paradox card
- Added `emv` commmands working for both contactless and smart cards (Merlok) - Added `emv` commmands working for both contactless and smart cards (Merlok)
- Added 'hf 15 snoop' (piwi)
## [v3.1.0][2018-10-10] ## [v3.1.0][2018-10-10]

View file

@ -36,8 +36,8 @@ static uint16_t BigBuf_hi = BIGBUF_SIZE;
static uint8_t *emulator_memory = NULL; static uint8_t *emulator_memory = NULL;
// trace related variables // trace related variables
static uint16_t traceLen = 0; static uint32_t traceLen = 0;
int tracing = 1; //Last global one.. todo static? static bool tracing = true;
// get the address of BigBuf // get the address of BigBuf
uint8_t *BigBuf_get_addr(void) uint8_t *BigBuf_get_addr(void)
@ -66,7 +66,7 @@ void BigBuf_Clear(void)
// clear ALL of BigBuf // clear ALL of BigBuf
void BigBuf_Clear_ext(bool verbose) void BigBuf_Clear_ext(bool verbose)
{ {
memset(BigBuf,0,BIGBUF_SIZE); memset(BigBuf, 0, BIGBUF_SIZE);
if (verbose) if (verbose)
Dbprintf("Buffer cleared (%i bytes)",BIGBUF_SIZE); Dbprintf("Buffer cleared (%i bytes)",BIGBUF_SIZE);
} }
@ -76,7 +76,7 @@ void BigBuf_Clear_EM(void){
void BigBuf_Clear_keep_EM(void) void BigBuf_Clear_keep_EM(void)
{ {
memset(BigBuf,0,BigBuf_hi); memset(BigBuf, 0, BigBuf_hi);
} }
// allocate a chunk of memory from BigBuf. We allocate high memory first. The unallocated memory // allocate a chunk of memory from BigBuf. We allocate high memory first. The unallocated memory
@ -162,8 +162,8 @@ bool RAMFUNC LogTrace(const uint8_t *btBytes, uint16_t iLen, uint32_t timestamp_
uint8_t *trace = BigBuf_get_addr(); uint8_t *trace = BigBuf_get_addr();
uint16_t num_paritybytes = (iLen-1)/8 + 1; // number of valid paritybytes in *parity uint32_t num_paritybytes = (iLen-1)/8 + 1; // number of valid paritybytes in *parity
uint16_t duration = timestamp_end - timestamp_start; uint32_t duration = timestamp_end - timestamp_start;
// Return when trace is full // Return when trace is full
uint16_t max_traceLen = BigBuf_max_traceLen(); uint16_t max_traceLen = BigBuf_max_traceLen();
@ -200,19 +200,23 @@ bool RAMFUNC LogTrace(const uint8_t *btBytes, uint16_t iLen, uint32_t timestamp_
// data bytes // data bytes
if (btBytes != NULL && iLen != 0) { if (btBytes != NULL && iLen != 0) {
memcpy(trace + traceLen, btBytes, iLen); for (int i = 0; i < iLen; i++) {
trace[traceLen++] = *btBytes++;
}
} }
traceLen += iLen;
// parity bytes // parity bytes
if (num_paritybytes != 0) { if (num_paritybytes != 0) {
if (parity != NULL) { if (parity != NULL) {
memcpy(trace + traceLen, parity, num_paritybytes); for (int i = 0; i < num_paritybytes; i++) {
trace[traceLen++] = *parity++;
}
} else { } else {
memset(trace + traceLen, 0x00, num_paritybytes); for (int i = 0; i < num_paritybytes; i++) {
trace[traceLen++] = 0x00;
}
} }
} }
traceLen += num_paritybytes;
return true; return true;
} }
@ -259,8 +263,9 @@ int LogTraceHitag(const uint8_t * btBytes, int iBits, int iSamples, uint32_t dwP
trace[traceLen++] = ((dwParity >> 24) & 0xff); trace[traceLen++] = ((dwParity >> 24) & 0xff);
trace[traceLen++] = iBits; trace[traceLen++] = iBits;
memcpy(trace + traceLen, btBytes, iLen); for (int i = 0; i < iLen; i++) {
traceLen += iLen; trace[traceLen++] = *btBytes++;
}
return true; return true;
} }

View file

@ -24,6 +24,7 @@
#include "legicrfsim.h" #include "legicrfsim.h"
#include "hitag2.h" #include "hitag2.h"
#include "hitagS.h" #include "hitagS.h"
#include "iso15693.h"
#include "lfsampling.h" #include "lfsampling.h"
#include "BigBuf.h" #include "BigBuf.h"
#include "mifareutil.h" #include "mifareutil.h"
@ -1115,8 +1116,9 @@ void UsbPacketReceived(uint8_t *packet, int len)
case CMD_ACQUIRE_RAW_ADC_SAMPLES_ISO_15693: case CMD_ACQUIRE_RAW_ADC_SAMPLES_ISO_15693:
AcquireRawAdcSamplesIso15693(); AcquireRawAdcSamplesIso15693();
break; break;
case CMD_RECORD_RAW_ADC_SAMPLES_ISO_15693:
RecordRawAdcSamplesIso15693(); case CMD_SNOOP_ISO_15693:
SnoopIso15693();
break; break;
case CMD_ISO_15693_COMMAND: case CMD_ISO_15693_COMMAND:

View file

@ -25,7 +25,6 @@
extern const uint8_t OddByteParity[256]; extern const uint8_t OddByteParity[256];
extern int rsamples; // = 0; extern int rsamples; // = 0;
extern int tracing; // = TRUE;
extern uint8_t trigger; extern uint8_t trigger;
// This may be used (sparingly) to declare a function to be copied to // This may be used (sparingly) to declare a function to be copied to
@ -101,7 +100,6 @@ void RAMFUNC SnoopIso14443b(void);
void SendRawCommand14443B(uint32_t, uint32_t, uint8_t, uint8_t[]); void SendRawCommand14443B(uint32_t, uint32_t, uint8_t, uint8_t[]);
// Also used in iclass.c // Also used in iclass.c
bool RAMFUNC LogTrace(const uint8_t *btBytes, uint16_t len, uint32_t timestamp_start, uint32_t timestamp_end, uint8_t *parity, bool readerToTag);
void GetParity(const uint8_t *pbtCmd, uint16_t len, uint8_t *parity); void GetParity(const uint8_t *pbtCmd, uint16_t len, uint8_t *parity);
void RAMFUNC SniffMifare(uint8_t param); void RAMFUNC SniffMifare(uint8_t param);
@ -150,15 +148,6 @@ void OnSuccess();
void OnError(uint8_t reason); void OnError(uint8_t reason);
/// iso15693.h
void RecordRawAdcSamplesIso15693(void);
void AcquireRawAdcSamplesIso15693(void);
void ReaderIso15693(uint32_t parameter); // Simulate an ISO15693 reader - greg
void SimTagIso15693(uint32_t parameter, uint8_t *uid); // simulate an ISO15693 tag - greg
void BruteforceIso15693Afi(uint32_t speed); // find an AFI of a tag - atrox
void DirectTag15693Command(uint32_t datalen,uint32_t speed, uint32_t recv, uint8_t data[]); // send arbitrary commands from CLI - atrox
void SetDebugIso15693(uint32_t flag);
/// iclass.h /// iclass.h
void RAMFUNC SnoopIClass(void); void RAMFUNC SnoopIClass(void);
void SimulateIClass(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain); void SimulateIClass(uint32_t arg0, uint32_t arg1, uint32_t arg2, uint8_t *datain);

View file

@ -61,6 +61,7 @@ void SetAdcMuxFor(uint32_t whichGpio);
#define FPGA_HF_READER_RX_XCORR_848_KHZ (1<<0) #define FPGA_HF_READER_RX_XCORR_848_KHZ (1<<0)
#define FPGA_HF_READER_RX_XCORR_SNOOP (1<<1) #define FPGA_HF_READER_RX_XCORR_SNOOP (1<<1)
#define FPGA_HF_READER_RX_XCORR_QUARTER_FREQ (1<<2) #define FPGA_HF_READER_RX_XCORR_QUARTER_FREQ (1<<2)
#define FPGA_HF_READER_RX_XCORR_AMPLITUDE (1<<3)
// Options for the HF simulated tag, how to modulate // Options for the HF simulated tag, how to modulate
#define FPGA_HF_SIMULATOR_NO_MODULATION (0<<0) #define FPGA_HF_SIMULATOR_NO_MODULATION (0<<0)
#define FPGA_HF_SIMULATOR_MODULATE_BPSK (1<<0) #define FPGA_HF_SIMULATOR_MODULATE_BPSK (1<<0)

View file

@ -751,12 +751,9 @@ void RAMFUNC SnoopIClass(void)
//if(!LogTrace(Uart.output,Uart.byteCnt, rsamples, Uart.parityBits,true)) break; //if(!LogTrace(Uart.output,Uart.byteCnt, rsamples, Uart.parityBits,true)) break;
//if(!LogTrace(NULL, 0, Uart.endTime*16 - DELAY_READER_AIR2ARM_AS_SNIFFER, 0, true)) break; //if(!LogTrace(NULL, 0, Uart.endTime*16 - DELAY_READER_AIR2ARM_AS_SNIFFER, 0, true)) break;
if(tracing) {
uint8_t parity[MAX_PARITY_SIZE]; uint8_t parity[MAX_PARITY_SIZE];
GetParity(Uart.output, Uart.byteCnt, parity); GetParity(Uart.output, Uart.byteCnt, parity);
LogTrace(Uart.output,Uart.byteCnt, time_start, time_stop, parity, true); LogTrace(Uart.output,Uart.byteCnt, time_start, time_stop, parity, true);
}
/* And ready to receive another command. */ /* And ready to receive another command. */
Uart.state = STATE_UNSYNCD; Uart.state = STATE_UNSYNCD;
@ -779,11 +776,9 @@ void RAMFUNC SnoopIClass(void)
rsamples = samples - Demod.samples; rsamples = samples - Demod.samples;
LED_B_ON(); LED_B_ON();
if(tracing) {
uint8_t parity[MAX_PARITY_SIZE]; uint8_t parity[MAX_PARITY_SIZE];
GetParity(Demod.output, Demod.len, parity); GetParity(Demod.output, Demod.len, parity);
LogTrace(Demod.output, Demod.len, time_start, time_stop, parity, false); LogTrace(Demod.output, Demod.len, time_start, time_stop, parity, false);
}
// And ready to receive another response. // And ready to receive another response.
memset(&Demod, 0, sizeof(Demod)); memset(&Demod, 0, sizeof(Demod));
@ -1322,7 +1317,6 @@ int doIClassSimulation( int simulationMode, uint8_t *reader_mac_buf)
t2r_time = GetCountSspClk(); t2r_time = GetCountSspClk();
} }
if (tracing) {
uint8_t parity[MAX_PARITY_SIZE]; uint8_t parity[MAX_PARITY_SIZE];
GetParity(receivedCmd, len, parity); GetParity(receivedCmd, len, parity);
LogTrace(receivedCmd,len, (r2t_time-time_0)<< 4, (r2t_time-time_0) << 4, parity, true); LogTrace(receivedCmd,len, (r2t_time-time_0)<< 4, (r2t_time-time_0) << 4, parity, true);
@ -1331,12 +1325,10 @@ int doIClassSimulation( int simulationMode, uint8_t *reader_mac_buf)
GetParity(trace_data, trace_data_size, parity); GetParity(trace_data, trace_data_size, parity);
LogTrace(trace_data, trace_data_size, (t2r_time-time_0) << 4, (t2r_time-time_0) << 4, parity, false); LogTrace(trace_data, trace_data_size, (t2r_time-time_0) << 4, (t2r_time-time_0) << 4, parity, false);
} }
if(!tracing) { if(!get_tracing()) {
DbpString("Trace full"); DbpString("Trace full");
//break; //break;
} }
}
} }
//Dbprintf("%x", cmdsRecvd); //Dbprintf("%x", cmdsRecvd);
@ -1509,11 +1501,9 @@ void ReaderTransmitIClass(uint8_t* frame, int len)
LED_A_ON(); LED_A_ON();
// Store reader command in buffer // Store reader command in buffer
if (tracing) {
uint8_t par[MAX_PARITY_SIZE]; uint8_t par[MAX_PARITY_SIZE];
GetParity(frame, len, par); GetParity(frame, len, par);
LogTrace(frame, len, rsamples, rsamples, par, true); LogTrace(frame, len, rsamples, rsamples, par, true);
}
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -1569,11 +1559,9 @@ int ReaderReceiveIClass(uint8_t* receivedAnswer)
int samples = 0; int samples = 0;
if (!GetIClassAnswer(receivedAnswer,160,&samples,0)) return false; if (!GetIClassAnswer(receivedAnswer,160,&samples,0)) return false;
rsamples += samples; rsamples += samples;
if (tracing) {
uint8_t parity[MAX_PARITY_SIZE]; uint8_t parity[MAX_PARITY_SIZE];
GetParity(receivedAnswer, Demod.len, parity); GetParity(receivedAnswer, Demod.len, parity);
LogTrace(receivedAnswer,Demod.len,rsamples,rsamples,parity,false); LogTrace(receivedAnswer,Demod.len,rsamples,rsamples,parity,false);
}
if(samples == 0) return false; if(samples == 0) return false;
return Demod.len; return Demod.len;
} }
@ -1715,7 +1703,7 @@ void ReaderIClass(uint8_t arg0) {
// if only looking for one card try 2 times if we missed it the first time // if only looking for one card try 2 times if we missed it the first time
if (try_once && tryCnt > 2) break; if (try_once && tryCnt > 2) break;
tryCnt++; tryCnt++;
if(!tracing) { if(!get_tracing()) {
DbpString("Trace full"); DbpString("Trace full");
break; break;
} }
@ -1828,7 +1816,7 @@ void ReaderIClass_Replay(uint8_t arg0, uint8_t *MAC) {
WDT_HIT(); WDT_HIT();
if(!tracing) { if(!get_tracing()) {
DbpString("Trace full"); DbpString("Trace full");
break; break;
} }

View file

@ -1220,7 +1220,7 @@ void SimulateIso14443aTag(int tagType, int uid_1st, int uid_2nd, byte_t* data)
EmSendPrecompiledCmd(p_response); EmSendPrecompiledCmd(p_response);
} }
if (!tracing) { if (!get_tracing()) {
Dbprintf("Trace Full. Simulation stopped."); Dbprintf("Trace Full. Simulation stopped.");
break; break;
} }
@ -1619,9 +1619,7 @@ void ReaderTransmitBitsPar(uint8_t* frame, uint16_t bits, uint8_t *par, uint32_t
LED_A_ON(); LED_A_ON();
// Log reader command in trace buffer // Log reader command in trace buffer
if (tracing) {
LogTrace(frame, nbytes(bits), LastTimeProxToAirStart*16 + DELAY_ARM2AIR_AS_READER, (LastTimeProxToAirStart + LastProxToAirDuration)*16 + DELAY_ARM2AIR_AS_READER, par, true); LogTrace(frame, nbytes(bits), LastTimeProxToAirStart*16 + DELAY_ARM2AIR_AS_READER, (LastTimeProxToAirStart + LastProxToAirDuration)*16 + DELAY_ARM2AIR_AS_READER, par, true);
}
} }
@ -1652,9 +1650,7 @@ void ReaderTransmit(uint8_t* frame, uint16_t len, uint32_t *timing)
static int ReaderReceiveOffset(uint8_t* receivedAnswer, uint16_t offset, uint8_t *parity) static int ReaderReceiveOffset(uint8_t* receivedAnswer, uint16_t offset, uint8_t *parity)
{ {
if (!GetIso14443aAnswerFromTag(receivedAnswer, parity, offset)) return false; if (!GetIso14443aAnswerFromTag(receivedAnswer, parity, offset)) return false;
if (tracing) {
LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false); LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false);
}
return Demod.len; return Demod.len;
} }
@ -1662,9 +1658,7 @@ static int ReaderReceiveOffset(uint8_t* receivedAnswer, uint16_t offset, uint8_t
int ReaderReceive(uint8_t *receivedAnswer, uint8_t *parity) int ReaderReceive(uint8_t *receivedAnswer, uint8_t *parity)
{ {
if (!GetIso14443aAnswerFromTag(receivedAnswer, parity, 0)) return false; if (!GetIso14443aAnswerFromTag(receivedAnswer, parity, 0)) return false;
if (tracing) {
LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false); LogTrace(receivedAnswer, Demod.len, Demod.startTime*16 - DELAY_AIR2ARM_AS_READER, Demod.endTime*16 - DELAY_AIR2ARM_AS_READER, parity, false);
}
return Demod.len; return Demod.len;
} }

View file

@ -386,10 +386,7 @@ void SimulateIso14443bTag(void)
break; break;
} }
if (tracing) { LogTrace(receivedCmd, len, 0, 0, NULL, true);
uint8_t parity[MAX_PARITY_SIZE];
LogTrace(receivedCmd, len, 0, 0, parity, true);
}
// Good, look at the command now. // Good, look at the command now.
if ( (len == sizeof(cmd1) && memcmp(receivedCmd, cmd1, len) == 0) if ( (len == sizeof(cmd1) && memcmp(receivedCmd, cmd1, len) == 0)
@ -463,10 +460,7 @@ void SimulateIso14443bTag(void)
} }
// trace the response: // trace the response:
if (tracing) { LogTrace(resp, respLen, 0, 0, NULL, false);
uint8_t parity[MAX_PARITY_SIZE];
LogTrace(resp, respLen, 0, 0, parity, false);
}
} }
} }
@ -763,9 +757,8 @@ static void GetSamplesFor14443bDemod(int n, bool quiet)
if (!quiet) Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Demod.len = %d, Demod.sumI = %d, Demod.sumQ = %d", maxBehindBy, samples, gotFrame, Demod.len, Demod.sumI, Demod.sumQ); if (!quiet) Dbprintf("max behindby = %d, samples = %d, gotFrame = %d, Demod.len = %d, Demod.sumI = %d, Demod.sumQ = %d", maxBehindBy, samples, gotFrame, Demod.len, Demod.sumI, Demod.sumQ);
//Tracing //Tracing
if (tracing && Demod.len > 0) { if (Demod.len > 0) {
uint8_t parity[MAX_PARITY_SIZE]; LogTrace(Demod.output, Demod.len, 0, 0, NULL, false);
LogTrace(Demod.output, Demod.len, 0, 0, parity, false);
} }
} }
@ -858,10 +851,7 @@ static void CodeAndTransmit14443bAsReader(const uint8_t *cmd, int len)
{ {
CodeIso14443bAsReader(cmd, len); CodeIso14443bAsReader(cmd, len);
TransmitFor14443b(); TransmitFor14443b();
if (tracing) { LogTrace(cmd,len, 0, 0, NULL, true);
uint8_t parity[MAX_PARITY_SIZE];
LogTrace(cmd,len, 0, 0, parity, true);
}
} }
/* Sends an APDU to the tag /* Sends an APDU to the tag
@ -1153,7 +1143,6 @@ void RAMFUNC SnoopIso14443b(void)
upTo = dmaBuf; upTo = dmaBuf;
lastRxCounter = ISO14443B_DMA_BUFFER_SIZE; lastRxCounter = ISO14443B_DMA_BUFFER_SIZE;
FpgaSetupSscDma((uint8_t*) dmaBuf, ISO14443B_DMA_BUFFER_SIZE); FpgaSetupSscDma((uint8_t*) dmaBuf, ISO14443B_DMA_BUFFER_SIZE);
uint8_t parity[MAX_PARITY_SIZE];
bool TagIsActive = false; bool TagIsActive = false;
bool ReaderIsActive = false; bool ReaderIsActive = false;
@ -1198,9 +1187,7 @@ void RAMFUNC SnoopIso14443b(void)
if (!TagIsActive) { // no need to try decoding reader data if the tag is sending if (!TagIsActive) { // no need to try decoding reader data if the tag is sending
if(Handle14443bUartBit(ci & 0x01)) { if(Handle14443bUartBit(ci & 0x01)) {
triggered = true; triggered = true;
if(tracing) { LogTrace(Uart.output, Uart.byteCnt, samples, samples, NULL, true);
LogTrace(Uart.output, Uart.byteCnt, samples, samples, parity, true);
}
/* And ready to receive another command. */ /* And ready to receive another command. */
UartReset(); UartReset();
/* And also reset the demod code, which might have been */ /* And also reset the demod code, which might have been */
@ -1209,9 +1196,7 @@ void RAMFUNC SnoopIso14443b(void)
} }
if(Handle14443bUartBit(cq & 0x01)) { if(Handle14443bUartBit(cq & 0x01)) {
triggered = true; triggered = true;
if(tracing) { LogTrace(Uart.output, Uart.byteCnt, samples, samples, NULL, true);
LogTrace(Uart.output, Uart.byteCnt, samples, samples, parity, true);
}
/* And ready to receive another command. */ /* And ready to receive another command. */
UartReset(); UartReset();
/* And also reset the demod code, which might have been */ /* And also reset the demod code, which might have been */
@ -1223,13 +1208,8 @@ void RAMFUNC SnoopIso14443b(void)
if(!ReaderIsActive && triggered) { // no need to try decoding tag data if the reader is sending or not yet triggered if(!ReaderIsActive && triggered) { // no need to try decoding tag data if the reader is sending or not yet triggered
if(Handle14443bSamplesDemod(ci/2, cq/2)) { if(Handle14443bSamplesDemod(ci/2, cq/2)) {
//Use samples as a time measurement //Use samples as a time measurement
if(tracing) LogTrace(Demod.output, Demod.len, samples, samples, NULL, false);
{
uint8_t parity[MAX_PARITY_SIZE];
LogTrace(Demod.output, Demod.len, samples, samples, parity, false);
}
// And ready to receive another response. // And ready to receive another response.
DemodReset(); DemodReset();
} }

File diff suppressed because it is too large Load diff

24
armsrc/iso15693.h Normal file
View file

@ -0,0 +1,24 @@
//-----------------------------------------------------------------------------
// Piwi - October 2018
//
// This code is licensed to you under the terms of the GNU GPL, version 2 or,
// at your option, any later version. See the LICENSE.txt file for the text of
// the license.
//-----------------------------------------------------------------------------
// Routines to support ISO 15693.
//-----------------------------------------------------------------------------
#ifndef __ISO15693_H
#define __ISO15693_H
#include <stdint.h>
void SnoopIso15693(void);
void AcquireRawAdcSamplesIso15693(void);
void ReaderIso15693(uint32_t parameter);
void SimTagIso15693(uint32_t parameter, uint8_t *uid);
void BruteforceIso15693Afi(uint32_t speed);
void DirectTag15693Command(uint32_t datalen,uint32_t speed, uint32_t recv, uint8_t data[]);
void SetDebugIso15693(uint32_t flag);
#endif

View file

@ -264,7 +264,7 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui
uint8_t parityBits = parityBytes[j>>3]; uint8_t parityBits = parityBytes[j>>3];
if (protocol != ISO_14443B && (isResponse || protocol == ISO_14443A) && (oddparity8(frame[j]) != ((parityBits >> (7-(j&0x0007))) & 0x01))) { if (protocol != ISO_14443B && (isResponse || protocol == ISO_14443A) && (oddparity8(frame[j]) != ((parityBits >> (7-(j&0x0007))) & 0x01))) {
snprintf(line[j/16]+(( j % 16) * 4),110, "%02x! ", frame[j]); snprintf(line[j/16]+(( j % 16) * 4), 110, " %02x!", frame[j]);
} else { } else {
snprintf(line[j/16]+(( j % 16) * 4), 110, " %02x ", frame[j]); snprintf(line[j/16]+(( j % 16) * 4), 110, " %02x ", frame[j]);
} }
@ -281,14 +281,11 @@ uint16_t printTraceLine(uint16_t tracepos, uint16_t traceLen, uint8_t *trace, ui
} }
} }
if(data_len == 0) if (data_len == 0) {
{ sprintf(line[0]," <empty trace - possible error>");
if(data_len == 0){
sprintf(line[0],"<empty trace - possible error>");
} }
}
//--- Draw the CRC column
//--- Draw the CRC column
char *crc = (crcStatus == 0 ? "!crc" : (crcStatus == 1 ? " ok " : " ")); char *crc = (crcStatus == 0 ? "!crc" : (crcStatus == 1 ? " ok " : " "));
EndOfTransmissionTimestamp = timestamp + duration; EndOfTransmissionTimestamp = timestamp + duration;

View file

@ -38,15 +38,54 @@
#include "protocols.h" #include "protocols.h"
#include "cmdmain.h" #include "cmdmain.h"
#define FrameSOF Iso15693FrameSOF
#define Logic0 Iso15693Logic0
#define Logic1 Iso15693Logic1
#define FrameEOF Iso15693FrameEOF
#define Crc(data,datalen) Iso15693Crc(data,datalen) #define Crc(data,datalen) Iso15693Crc(data,datalen)
#define AddCrc(data,datalen) Iso15693AddCrc(data,datalen) #define AddCrc(data,datalen) Iso15693AddCrc(data,datalen)
#define sprintUID(target,uid) Iso15693sprintUID(target,uid) #define sprintUID(target,uid) Iso15693sprintUID(target,uid)
// SOF defined as
// 1) Unmodulated time of 56.64us
// 2) 24 pulses of 423.75khz
// 3) logic '1' (unmodulated for 18.88us followed by 8 pulses of 423.75khz)
static const int Iso15693FrameSOF[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, -1, -1,
-1, -1, -1, -1,
1, 1, 1, 1,
1, 1, 1, 1
};
static const int Iso15693Logic0[] = {
1, 1, 1, 1,
1, 1, 1, 1,
-1, -1, -1, -1,
-1, -1, -1, -1
};
static const int Iso15693Logic1[] = {
-1, -1, -1, -1,
-1, -1, -1, -1,
1, 1, 1, 1,
1, 1, 1, 1
};
// EOF defined as
// 1) logic '0' (8 pulses of 423.75khz followed by unmodulated for 18.88us)
// 2) 24 pulses of 423.75khz
// 3) Unmodulated time of 56.64us
static const int Iso15693FrameEOF[] = {
1, 1, 1, 1,
1, 1, 1, 1,
-1, -1, -1, -1,
-1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
// structure and database for uid -> tagtype lookups // structure and database for uid -> tagtype lookups
typedef struct { typedef struct {
uint64_t uid; uint64_t uid;
@ -293,8 +332,8 @@ int CmdHF15Demod(const char *Cmd)
// First, correlate for SOF // First, correlate for SOF
for (i = 0; i < 200; i++) { for (i = 0; i < 200; i++) {
int corr = 0; int corr = 0;
for (j = 0; j < arraylen(FrameSOF); j += skip) { for (j = 0; j < arraylen(Iso15693FrameSOF); j += skip) {
corr += FrameSOF[j] * GraphBuffer[i + (j / skip)]; corr += Iso15693FrameSOF[j] * GraphBuffer[i + (j / skip)];
} }
if (corr > max) { if (corr > max) {
max = corr; max = corr;
@ -302,28 +341,28 @@ int CmdHF15Demod(const char *Cmd)
} }
} }
PrintAndLog("SOF at %d, correlation %d", maxPos, PrintAndLog("SOF at %d, correlation %d", maxPos,
max / (arraylen(FrameSOF) / skip)); max / (arraylen(Iso15693FrameSOF) / skip));
i = maxPos + arraylen(FrameSOF) / skip; i = maxPos + arraylen(Iso15693FrameSOF) / skip;
int k = 0; int k = 0;
uint8_t outBuf[20]; uint8_t outBuf[20];
memset(outBuf, 0, sizeof(outBuf)); memset(outBuf, 0, sizeof(outBuf));
uint8_t mask = 0x01; uint8_t mask = 0x01;
for (;;) { for (;;) {
int corr0 = 0, corr00 = 0, corr01 = 0, corr1 = 0, corrEOF = 0; int corr0 = 0, corr00 = 0, corr01 = 0, corr1 = 0, corrEOF = 0;
for(j = 0; j < arraylen(Logic0); j += skip) { for(j = 0; j < arraylen(Iso15693Logic0); j += skip) {
corr0 += Logic0[j]*GraphBuffer[i+(j/skip)]; corr0 += Iso15693Logic0[j]*GraphBuffer[i+(j/skip)];
} }
corr01 = corr00 = corr0; corr01 = corr00 = corr0;
for(j = 0; j < arraylen(Logic0); j += skip) { for(j = 0; j < arraylen(Iso15693Logic0); j += skip) {
corr00 += Logic0[j]*GraphBuffer[i+arraylen(Logic0)/skip+(j/skip)]; corr00 += Iso15693Logic0[j]*GraphBuffer[i+arraylen(Iso15693Logic0)/skip+(j/skip)];
corr01 += Logic1[j]*GraphBuffer[i+arraylen(Logic0)/skip+(j/skip)]; corr01 += Iso15693Logic1[j]*GraphBuffer[i+arraylen(Iso15693Logic0)/skip+(j/skip)];
} }
for(j = 0; j < arraylen(Logic1); j += skip) { for(j = 0; j < arraylen(Iso15693Logic1); j += skip) {
corr1 += Logic1[j]*GraphBuffer[i+(j/skip)]; corr1 += Iso15693Logic1[j]*GraphBuffer[i+(j/skip)];
} }
for(j = 0; j < arraylen(FrameEOF); j += skip) { for(j = 0; j < arraylen(Iso15693FrameEOF); j += skip) {
corrEOF += FrameEOF[j]*GraphBuffer[i+(j/skip)]; corrEOF += Iso15693FrameEOF[j]*GraphBuffer[i+(j/skip)];
} }
// Even things out by the length of the target waveform. // Even things out by the length of the target waveform.
corr00 *= 2; corr00 *= 2;
@ -335,17 +374,17 @@ int CmdHF15Demod(const char *Cmd)
PrintAndLog("EOF at %d", i); PrintAndLog("EOF at %d", i);
break; break;
} else if (corr1 > corr0) { } else if (corr1 > corr0) {
i += arraylen(Logic1) / skip; i += arraylen(Iso15693Logic1) / skip;
outBuf[k] |= mask; outBuf[k] |= mask;
} else { } else {
i += arraylen(Logic0) / skip; i += arraylen(Iso15693Logic0) / skip;
} }
mask <<= 1; mask <<= 1;
if (mask == 0) { if (mask == 0) {
k++; k++;
mask = 0x01; mask = 0x01;
} }
if ((i + (int)arraylen(FrameEOF)) >= GraphTraceLen) { if ((i + (int)arraylen(Iso15693FrameEOF)) >= GraphTraceLen) {
PrintAndLog("ran off end!"); PrintAndLog("ran off end!");
break; break;
} }
@ -374,10 +413,9 @@ int CmdHF15Read(const char *Cmd)
} }
// Record Activity without enabling carrier // Record Activity without enabling carrier
// TODO: currently it DOES enable the carrier int CmdHF15Snoop(const char *Cmd)
int CmdHF15Record(const char *Cmd)
{ {
UsbCommand c = {CMD_RECORD_RAW_ADC_SAMPLES_ISO_15693}; UsbCommand c = {CMD_SNOOP_ISO_15693};
SendCommand(&c); SendCommand(&c);
return 0; return 0;
} }
@ -514,7 +552,7 @@ static command_t CommandTable15[] =
{"help", CmdHF15Help, 1, "This help"}, {"help", CmdHF15Help, 1, "This help"},
{"demod", CmdHF15Demod, 1, "Demodulate ISO15693 from tag"}, {"demod", CmdHF15Demod, 1, "Demodulate ISO15693 from tag"},
{"read", CmdHF15Read, 0, "Read HF tag (ISO 15693)"}, {"read", CmdHF15Read, 0, "Read HF tag (ISO 15693)"},
{"record", CmdHF15Record, 0, "Record Samples (ISO 15693)"}, // atrox {"snoop", CmdHF15Snoop, 0, "Eavesdrop ISO 15693 communications"},
{"reader", CmdHF15Reader, 0, "Act like an ISO15693 reader"}, {"reader", CmdHF15Reader, 0, "Act like an ISO15693 reader"},
{"sim", CmdHF15Sim, 0, "Fake an ISO15693 tag"}, {"sim", CmdHF15Sim, 0, "Fake an ISO15693 tag"},
{"cmd", CmdHF15Cmd, 0, "Send direct commands to ISO15693 tag"}, {"cmd", CmdHF15Cmd, 0, "Send direct commands to ISO15693 tag"},

View file

@ -14,57 +14,4 @@ int Iso15693AddCrc(uint8_t *req, int n);
char* Iso15693sprintUID(char *target,uint8_t *uid); char* Iso15693sprintUID(char *target,uint8_t *uid);
unsigned short iclass_crc16(char *data_p, unsigned short length); unsigned short iclass_crc16(char *data_p, unsigned short length);
//-----------------------------------------------------------------------------
// Map a sequence of octets (~layer 2 command) into the set of bits to feed
// to the FPGA, to transmit that command to the tag.
// Mode: highspeed && one subcarrier (ASK)
//-----------------------------------------------------------------------------
// The sampling rate is 106.353 ksps/s, for T = 18.8 us
// SOF defined as
// 1) Unmodulated time of 56.64us
// 2) 24 pulses of 423.75khz
// 3) logic '1' (unmodulated for 18.88us followed by 8 pulses of 423.75khz)
static const int Iso15693FrameSOF[] = {
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, -1, -1,
-1, -1, -1, -1,
1, 1, 1, 1,
1, 1, 1, 1
};
static const int Iso15693Logic0[] = {
1, 1, 1, 1,
1, 1, 1, 1,
-1, -1, -1, -1,
-1, -1, -1, -1
};
static const int Iso15693Logic1[] = {
-1, -1, -1, -1,
-1, -1, -1, -1,
1, 1, 1, 1,
1, 1, 1, 1
};
// EOF defined as
// 1) logic '0' (8 pulses of 423.75khz followed by unmodulated for 18.88us)
// 2) 24 pulses of 423.75khz
// 3) Unmodulated time of 56.64us
static const int Iso15693FrameEOF[] = {
1, 1, 1, 1,
1, 1, 1, 1,
-1, -1, -1, -1,
-1, -1, -1, -1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
};
#endif #endif

Binary file not shown.

View file

@ -75,6 +75,8 @@ wire hi_read_rx_xcorr_848 = conf_word[0];
wire hi_read_rx_xcorr_snoop = conf_word[1]; wire hi_read_rx_xcorr_snoop = conf_word[1];
// divide subcarrier frequency by 4 // divide subcarrier frequency by 4
wire hi_read_rx_xcorr_quarter = conf_word[2]; wire hi_read_rx_xcorr_quarter = conf_word[2];
// send amplitude only instead of ci/cq pair
wire hi_read_rx_xcorr_amplitude = conf_word[3];
// For the high-frequency simulated tag: what kind of modulation to use. // For the high-frequency simulated tag: what kind of modulation to use.
wire [2:0] hi_simulate_mod_type = conf_word[2:0]; wire [2:0] hi_simulate_mod_type = conf_word[2:0];
@ -102,7 +104,7 @@ hi_read_rx_xcorr hrxc(
hrxc_ssp_frame, hrxc_ssp_din, ssp_dout, hrxc_ssp_clk, hrxc_ssp_frame, hrxc_ssp_din, ssp_dout, hrxc_ssp_clk,
cross_hi, cross_lo, cross_hi, cross_lo,
hrxc_dbg, hrxc_dbg,
hi_read_rx_xcorr_848, hi_read_rx_xcorr_snoop, hi_read_rx_xcorr_quarter hi_read_rx_xcorr_848, hi_read_rx_xcorr_snoop, hi_read_rx_xcorr_quarter, hi_read_rx_xcorr_amplitude
); );
hi_simulate hs( hi_simulate hs(

View file

@ -10,7 +10,7 @@ module hi_read_rx_xcorr(
ssp_frame, ssp_din, ssp_dout, ssp_clk, ssp_frame, ssp_din, ssp_dout, ssp_clk,
cross_hi, cross_lo, cross_hi, cross_lo,
dbg, dbg,
xcorr_is_848, snoop, xcorr_quarter_freq xcorr_is_848, snoop, xcorr_quarter_freq, hi_read_rx_xcorr_amplitude
); );
input pck0, ck_1356meg, ck_1356megb; input pck0, ck_1356meg, ck_1356megb;
output pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4; output pwr_lo, pwr_hi, pwr_oe1, pwr_oe2, pwr_oe3, pwr_oe4;
@ -20,7 +20,7 @@ module hi_read_rx_xcorr(
output ssp_frame, ssp_din, ssp_clk; output ssp_frame, ssp_din, ssp_clk;
input cross_hi, cross_lo; input cross_hi, cross_lo;
output dbg; output dbg;
input xcorr_is_848, snoop, xcorr_quarter_freq; input xcorr_is_848, snoop, xcorr_quarter_freq, hi_read_rx_xcorr_amplitude;
// Carrier is steady on through this, unless we're snooping. // Carrier is steady on through this, unless we're snooping.
assign pwr_hi = ck_1356megb & (~snoop); assign pwr_hi = ck_1356megb & (~snoop);
@ -83,11 +83,46 @@ reg signed [13:0] corr_q_accum;
// we will report maximum 8 significant bits // we will report maximum 8 significant bits
reg signed [7:0] corr_i_out; reg signed [7:0] corr_i_out;
reg signed [7:0] corr_q_out; reg signed [7:0] corr_q_out;
// clock and frame signal for communication to ARM // clock and frame signal for communication to ARM
reg ssp_clk; reg ssp_clk;
reg ssp_frame; reg ssp_frame;
// the amplitude of the subcarrier is sqrt(ci^2 + cq^2).
// approximate by amplitude = max(|ci|,|cq|) + 1/2*min(|ci|,|cq|)
reg [13:0] corr_amplitude, abs_ci, abs_cq, max_ci_cq, min_ci_cq;
always @(corr_i_accum or corr_q_accum)
begin
if (corr_i_accum[13] == 1'b0)
abs_ci <= corr_i_accum;
else
abs_ci <= -corr_i_accum;
if (corr_q_accum[13] == 1'b0)
abs_cq <= corr_q_accum;
else
abs_cq <= -corr_q_accum;
if (abs_ci > abs_cq)
begin
max_ci_cq <= abs_ci;
min_ci_cq <= abs_cq;
end
else
begin
max_ci_cq <= abs_cq;
min_ci_cq <= abs_ci;
end
corr_amplitude <= max_ci_cq + min_ci_cq/2;
end
// The subcarrier reference signals // The subcarrier reference signals
reg subcarrier_I; reg subcarrier_I;
reg subcarrier_Q; reg subcarrier_Q;
@ -111,17 +146,26 @@ begin
end end
end end
// ADC data appears on the rising edge, so sample it on the falling edge // ADC data appears on the rising edge, so sample it on the falling edge
always @(negedge adc_clk) always @(negedge adc_clk)
begin begin
// These are the correlators: we correlate against in-phase and quadrature // These are the correlators: we correlate against in-phase and quadrature
// versions of our reference signal, and keep the (signed) result to // versions of our reference signal, and keep the (signed) results or the
// send out later over the SSP. // resulting amplitude to send out later over the SSP.
if(corr_i_cnt == 6'd0) if(corr_i_cnt == 6'd0)
begin begin
if(snoop) if(snoop)
begin begin
// Send 7 most significant bits of tag signal (signed), plus 1 bit reader signal if (hi_read_rx_xcorr_amplitude)
begin
// send amplitude plus 2 bits reader signal
corr_i_out <= corr_amplitude[13:6];
corr_q_out <= {corr_amplitude[5:0], after_hysteresis_prev_prev, after_hysteresis_prev};
end
else
begin
// Send 7 most significant bits of in phase tag signal (signed), plus 1 bit reader signal
if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111)
corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev}; corr_i_out <= {corr_i_accum[11:5], after_hysteresis_prev_prev};
else // truncate to maximum value else // truncate to maximum value
@ -129,6 +173,7 @@ begin
corr_i_out <= {7'b0111111, after_hysteresis_prev_prev}; corr_i_out <= {7'b0111111, after_hysteresis_prev_prev};
else else
corr_i_out <= {7'b1000000, after_hysteresis_prev_prev}; corr_i_out <= {7'b1000000, after_hysteresis_prev_prev};
// Send 7 most significant bits of quadrature phase tag signal (signed), plus 1 bit reader signal
if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111)
corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev}; corr_q_out <= {corr_q_accum[11:5], after_hysteresis_prev};
else // truncate to maximum value else // truncate to maximum value
@ -136,11 +181,19 @@ begin
corr_q_out <= {7'b0111111, after_hysteresis_prev}; corr_q_out <= {7'b0111111, after_hysteresis_prev};
else else
corr_q_out <= {7'b1000000, after_hysteresis_prev}; corr_q_out <= {7'b1000000, after_hysteresis_prev};
after_hysteresis_prev_prev <= after_hysteresis; end
end end
else else
begin begin
// Send 8 bits of tag signal if (hi_read_rx_xcorr_amplitude)
begin
// send amplitude
corr_i_out <= {2'b00, corr_amplitude[13:8]};
corr_q_out <= corr_amplitude[7:0];
end
else
begin
// Send 8 bits of in phase tag signal
if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111) if (corr_i_accum[13:11] == 3'b000 || corr_i_accum[13:11] == 3'b111)
corr_i_out <= corr_i_accum[11:4]; corr_i_out <= corr_i_accum[11:4];
else // truncate to maximum value else // truncate to maximum value
@ -148,6 +201,7 @@ begin
corr_i_out <= 8'b01111111; corr_i_out <= 8'b01111111;
else else
corr_i_out <= 8'b10000000; corr_i_out <= 8'b10000000;
// Send 8 bits of quadrature phase tag signal
if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111) if (corr_q_accum[13:11] == 3'b000 || corr_q_accum[13:11] == 3'b111)
corr_q_out <= corr_q_accum[11:4]; corr_q_out <= corr_q_accum[11:4];
else // truncate to maximum value else // truncate to maximum value
@ -156,6 +210,10 @@ begin
else else
corr_q_out <= 8'b10000000; corr_q_out <= 8'b10000000;
end end
end
// for each Q/I pair report two reader signal samples when sniffing. Store the 1st.
after_hysteresis_prev_prev <= after_hysteresis;
// Initialize next correlation. // Initialize next correlation.
// Both I and Q reference signals are high when corr_i_nct == 0. Therefore need to accumulate. // Both I and Q reference signals are high when corr_i_nct == 0. Therefore need to accumulate.
corr_i_accum <= $signed({1'b0,adc_d}); corr_i_accum <= $signed({1'b0,adc_d});
@ -172,16 +230,16 @@ begin
corr_q_accum <= corr_q_accum + $signed({1'b0,adc_d}); corr_q_accum <= corr_q_accum + $signed({1'b0,adc_d});
else else
corr_q_accum <= corr_q_accum - $signed({1'b0,adc_d}); corr_q_accum <= corr_q_accum - $signed({1'b0,adc_d});
end end
// for each Q/I pair report two reader signal samples when sniffing // for each Q/I pair report two reader signal samples when sniffing. Store the 2nd.
if(corr_i_cnt == 6'd32) if(corr_i_cnt == 6'd32)
after_hysteresis_prev <= after_hysteresis; after_hysteresis_prev <= after_hysteresis;
// Then the result from last time is serialized and send out to the ARM. // Then the result from last time is serialized and send out to the ARM.
// We get one report each cycle, and each report is 16 bits, so the // We get one report each cycle, and each report is 16 bits, so the
// ssp_clk should be the adc_clk divided by 64/16 = 4. // ssp_clk should be the adc_clk divided by 64/16 = 4.
// ssp_clk frequency = 13,56MHz / 4 = 3.39MHz
if(corr_i_cnt[1:0] == 2'b10) if(corr_i_cnt[1:0] == 2'b10)
ssp_clk <= 1'b0; ssp_clk <= 1'b0;

View file

@ -125,7 +125,7 @@ typedef struct{
#define CMD_ISO_14443B_COMMAND 0x0305 #define CMD_ISO_14443B_COMMAND 0x0305
#define CMD_READER_ISO_15693 0x0310 #define CMD_READER_ISO_15693 0x0310
#define CMD_SIMTAG_ISO_15693 0x0311 #define CMD_SIMTAG_ISO_15693 0x0311
#define CMD_RECORD_RAW_ADC_SAMPLES_ISO_15693 0x0312 #define CMD_SNOOP_ISO_15693 0x0312
#define CMD_ISO_15693_COMMAND 0x0313 #define CMD_ISO_15693_COMMAND 0x0313
#define CMD_ISO_15693_COMMAND_DONE 0x0314 #define CMD_ISO_15693_COMMAND_DONE 0x0314
#define CMD_ISO_15693_FIND_AFI 0x0315 #define CMD_ISO_15693_FIND_AFI 0x0315