Merge branch 'feature/new-architecture'

This commit is contained in:
michael
2026-01-28 15:12:08 -08:00
2 changed files with 232 additions and 213 deletions

View File

@@ -219,102 +219,102 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
{transponder && (chipInfo || chipFamilyInfo) && ( {transponder && (chipInfo || chipFamilyInfo) && (
<AnimatedSection delay={delays.aboutChip}> <AnimatedSection delay={delays.aboutChip}>
<Surface style={styles.chipInfoCard} elevation={1}> <Surface style={styles.chipInfoCard} elevation={1}>
<TouchableOpacity <TouchableOpacity
onPress={() => setShowChipInfo(!showChipInfo)} onPress={() => setShowChipInfo(!showChipInfo)}
style={styles.chipInfoHeader} style={styles.chipInfoHeader}
activeOpacity={0.7}> activeOpacity={0.7}>
<Text variant="labelLarge" style={styles.chipInfoLabel}> <Text variant="labelLarge" style={styles.chipInfoLabel}>
ABOUT THIS CHIP ABOUT THIS CHIP
</Text> </Text>
<Text style={styles.expandIndicator}> <Text style={styles.expandIndicator}>
{showChipInfo ? '▼' : '▶'} {showChipInfo ? '▼' : '▶'}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
{showChipInfo && ( {showChipInfo && (
<> <>
<Divider style={[styles.divider, { backgroundColor: DTColors.modeOther }]} /> <Divider style={[styles.divider, { backgroundColor: DTColors.modeOther }]} />
{/* Family description */} {/* Family description */}
{chipFamilyInfo && ( {chipFamilyInfo && (
<Text variant="bodyMedium" style={styles.chipFamilyDescription}> <Text variant="bodyMedium" style={styles.chipFamilyDescription}>
{chipFamilyInfo} {chipFamilyInfo}
</Text>
)}
{/* Detailed chip info */}
{chipInfo && (
<>
<Text variant="bodyMedium" style={styles.chipDescription}>
{chipInfo.description}
</Text> </Text>
)}
{/* Detailed chip info */}
{chipInfo && (
<>
<Text variant="bodyMedium" style={styles.chipDescription}>
{chipInfo.description}
</Text>
{chipInfo.memoryNote && (
<View style={styles.chipInfoRow}>
<Text variant="bodySmall" style={styles.chipInfoRowLabel}>
Memory:
</Text>
<Text variant="bodySmall" style={styles.chipInfoRowValue}>
{chipInfo.memoryNote}
</Text>
</View>
)}
{chipInfo.memoryNote && (
<View style={styles.chipInfoRow}> <View style={styles.chipInfoRow}>
<Text variant="bodySmall" style={styles.chipInfoRowLabel}> <Text variant="bodySmall" style={styles.chipInfoRowLabel}>
Memory: Security:
</Text> </Text>
<Text variant="bodySmall" style={styles.chipInfoRowValue}> <Text
{chipInfo.memoryNote} variant="bodySmall"
style={[
styles.chipInfoRowValue,
{
color:
chipInfo.securityLevel === 'high'
? DTColors.modeSuccess
: chipInfo.securityLevel === 'medium'
? DTColors.modeEmphasis
: DTColors.modeWarning,
},
]}>
{chipInfo.securityLevel.toUpperCase()}
</Text> </Text>
</View> </View>
)}
<View style={styles.chipInfoRow}> <Text variant="bodySmall" style={styles.securityNote}>
<Text variant="bodySmall" style={styles.chipInfoRowLabel}> {getSecurityLevelDescription(chipInfo.securityLevel)}
Security:
</Text> </Text>
<Text
variant="bodySmall"
style={[
styles.chipInfoRowValue,
{
color:
chipInfo.securityLevel === 'high'
? DTColors.modeSuccess
: chipInfo.securityLevel === 'medium'
? DTColors.modeEmphasis
: DTColors.modeWarning,
},
]}>
{chipInfo.securityLevel.toUpperCase()}
</Text>
</View>
<Text variant="bodySmall" style={styles.securityNote}> {chipInfo.commonUses.length > 0 && (
{getSecurityLevelDescription(chipInfo.securityLevel)} <>
</Text> <Text variant="labelSmall" style={styles.chipInfoSectionLabel}>
COMMON USES
{chipInfo.commonUses.length > 0 && (
<>
<Text variant="labelSmall" style={styles.chipInfoSectionLabel}>
COMMON USES
</Text>
{chipInfo.commonUses.map((use, idx) => (
<Text key={idx} variant="bodySmall" style={styles.chipInfoListItem}>
{use}
</Text> </Text>
))} {chipInfo.commonUses.map((use, idx) => (
</> <Text key={idx} variant="bodySmall" style={styles.chipInfoListItem}>
)} {use}
</Text>
))}
</>
)}
{chipInfo.capabilities.length > 0 && ( {chipInfo.capabilities.length > 0 && (
<> <>
<Text variant="labelSmall" style={styles.chipInfoSectionLabel}> <Text variant="labelSmall" style={styles.chipInfoSectionLabel}>
CAPABILITIES CAPABILITIES
</Text>
{chipInfo.capabilities.map((cap, idx) => (
<Text key={idx} variant="bodySmall" style={styles.chipInfoListItem}>
{cap}
</Text> </Text>
))} {chipInfo.capabilities.map((cap, idx) => (
</> <Text key={idx} variant="bodySmall" style={styles.chipInfoListItem}>
)} {cap}
</> </Text>
)} ))}
</> </>
)} )}
</Surface> </>
)}
</>
)}
</Surface>
</AnimatedSection> </AnimatedSection>
)} )}
@@ -341,6 +341,9 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
textStyle={styles.formFactorChipText}> textStyle={styles.formFactorChipText}>
{product.formFactor.replace('_', ' ')} {product.formFactor.replace('_', ' ')}
</DTChip> </DTChip>
{product.formFactor.startsWith("X") && <Text style={styles.chipDescription}>Injectable</Text>}
{product.formFactor.startsWith("B") && <Text style={styles.chipDescription}>Large, rigid</Text>}
{product.formFactor.startsWith("F") && <Text style={styles.chipDescription}>Large, semi-rigid</Text>}
</View> </View>
<Text variant="bodySmall" style={styles.productDescription}> <Text variant="bodySmall" style={styles.productDescription}>
@@ -390,28 +393,28 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
{transponder && matchResult && matchResult.exactMatches.length === 0 && matchResult.cloneTargets.length === 0 && ( {transponder && matchResult && matchResult.exactMatches.length === 0 && matchResult.cloneTargets.length === 0 && (
<AnimatedSection delay={delays.noMatch}> <AnimatedSection delay={delays.noMatch}>
<Surface style={styles.noMatchCard} elevation={1}> <Surface style={styles.noMatchCard} elevation={1}>
<Text variant="labelLarge" style={styles.noMatchLabel}> <Text variant="labelLarge" style={styles.noMatchLabel}>
NO DIRECT MATCH NO DIRECT MATCH
</Text> </Text>
<Divider style={[styles.divider, { backgroundColor: DTColors.modeOther }]} /> <Divider style={[styles.divider, { backgroundColor: DTColors.modeOther }]} />
<Text variant="bodyMedium" style={styles.noMatchText}> <Text variant="bodyMedium" style={styles.noMatchText}>
{getMatchSummary(matchResult, transponder.chipName)} {getMatchSummary(matchResult, transponder.chipName)}
</Text> </Text>
{matchResult.familyMatches.length > 0 && ( {matchResult.familyMatches.length > 0 && (
<> <>
<Text variant="bodySmall" style={styles.familyMatchesLabel}> <Text variant="bodySmall" style={styles.familyMatchesLabel}>
Related products in the same chip family: Related products in the same chip family:
</Text>
{matchResult.familyMatches.slice(0, 2).map(product => (
<Text key={product.id} variant="bodySmall" style={styles.familyMatchItem}>
{product.name}
</Text> </Text>
))} {matchResult.familyMatches.slice(0, 2).map(product => (
</> <Text key={product.id} variant="bodySmall" style={styles.familyMatchItem}>
)} {product.name}
</Surface> </Text>
))}
</>
)}
</Surface>
</AnimatedSection> </AnimatedSection>
)} )}
@@ -419,29 +422,29 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
{transponder?.sakSwapInfo?.hasSakSwap && ( {transponder?.sakSwapInfo?.hasSakSwap && (
<AnimatedSection delay={delays.sakSwap}> <AnimatedSection delay={delays.sakSwap}>
<Surface style={styles.sakSwapCard} elevation={1}> <Surface style={styles.sakSwapCard} elevation={1}>
<Text variant="labelLarge" style={styles.sakSwapLabel}> <Text variant="labelLarge" style={styles.sakSwapLabel}>
SAK SWAP DETECTED SAK SWAP DETECTED
</Text> </Text>
<Divider style={[styles.divider, { backgroundColor: DTColors.modeEmphasis }]} /> <Divider style={[styles.divider, { backgroundColor: DTColors.modeEmphasis }]} />
<Text variant="bodyLarge" style={styles.sakSwapType}> <Text variant="bodyLarge" style={styles.sakSwapType}>
{transponder.sakSwapInfo.swapType?.replace(/_/g, ' ').toUpperCase()} {transponder.sakSwapInfo.swapType?.replace(/_/g, ' ').toUpperCase()}
</Text> </Text>
<Text variant="bodyMedium" style={styles.sakSwapDescription}> <Text variant="bodyMedium" style={styles.sakSwapDescription}>
{transponder.sakSwapInfo.description} {transponder.sakSwapInfo.description}
</Text> </Text>
{transponder.sakSwapInfo.notes && transponder.sakSwapInfo.notes.length > 0 && ( {transponder.sakSwapInfo.notes && transponder.sakSwapInfo.notes.length > 0 && (
<View style={styles.sakSwapNotes}> <View style={styles.sakSwapNotes}>
{transponder.sakSwapInfo.notes.map((note, index) => ( {transponder.sakSwapInfo.notes.map((note, index) => (
<Text key={index} variant="bodySmall" style={styles.sakSwapNote}> <Text key={index} variant="bodySmall" style={styles.sakSwapNote}>
{note} {note}
</Text> </Text>
))} ))}
</View> </View>
)} )}
</Surface> </Surface>
</AnimatedSection> </AnimatedSection>
)} )}
@@ -449,90 +452,90 @@ export function ResultScreen({ route, navigation }: ResultScreenProps) {
<AnimatedSection delay={delays.rawData}> <AnimatedSection delay={delays.rawData}>
<DTCard mode="normal" title="RAW TAG DATA" style={{ marginBottom: 20 }}> <DTCard mode="normal" title="RAW TAG DATA" style={{ marginBottom: 20 }}>
{tagData ? ( {tagData ? (
<> <>
<View style={styles.dataRow}>
<Text variant="bodyLarge" style={styles.dataLabel}>
UID
</Text>
<Text variant="bodyMedium" style={styles.dataValue}>
{tagData.uid || 'N/A'}
</Text>
</View>
<View style={styles.dataRow}>
<Text variant="bodyLarge" style={styles.dataLabel}>
TECHNOLOGIES
</Text>
{
tagData.techTypes.map((t, i) =>
<Text key={i} variant="bodyMedium" style={styles.dataValue}>
{t}
</Text>)
}
</View>
{tagData.sak !== undefined && (
<View style={styles.dataRow}> <View style={styles.dataRow}>
<Text variant="bodyMedium" style={styles.dataLabel}> <Text variant="bodyLarge" style={styles.dataLabel}>
SAK UID
</Text> </Text>
<Text variant="bodyLarge" style={styles.dataValue}> <Text variant="bodyMedium" style={styles.dataValue}>
0x{tagData.sak.toString(16).toUpperCase().padStart(2, '0')} {tagData.uid || 'N/A'}
</Text> </Text>
</View> </View>
)}
{tagData.atqa && (
<View style={styles.dataRow}> <View style={styles.dataRow}>
<Text variant="bodyMedium" style={styles.dataLabel}> <Text variant="bodyLarge" style={styles.dataLabel}>
ATQA TECHNOLOGIES
</Text>
<Text variant="bodyLarge" style={styles.dataValue}>
{tagData.atqa}
</Text> </Text>
{
tagData.techTypes.map((t, i) =>
<Text key={i} variant="bodyMedium" style={styles.dataValue}>
{t}
</Text>)
}
</View> </View>
)}
{tagData.historicalBytes && ( {tagData.sak !== undefined && (
<View style={styles.dataRow}> <View style={styles.dataRow}>
<Text variant="bodyMedium" style={styles.dataLabel}> <Text variant="bodyMedium" style={styles.dataLabel}>
HISTORICAL BYTES SAK
</Text> </Text>
<Text variant="bodyLarge" style={styles.dataValue}> <Text variant="bodyLarge" style={styles.dataValue}>
{tagData.historicalBytes} 0x{tagData.sak.toString(16).toUpperCase().padStart(2, '0')}
</Text> </Text>
</View> </View>
)} )}
</>
) : ( {tagData.atqa && (
<Text variant="bodyLarge" style={styles.noData}> <View style={styles.dataRow}>
No tag data available <Text variant="bodyMedium" style={styles.dataLabel}>
</Text> ATQA
)} </Text>
</DTCard> <Text variant="bodyLarge" style={styles.dataValue}>
{tagData.atqa}
</Text>
</View>
)}
{tagData.historicalBytes && (
<View style={styles.dataRow}>
<Text variant="bodyMedium" style={styles.dataLabel}>
HISTORICAL BYTES
</Text>
<Text variant="bodyLarge" style={styles.dataValue}>
{tagData.historicalBytes}
</Text>
</View>
)}
</>
) : (
<Text variant="bodyLarge" style={styles.noData}>
No tag data available
</Text>
)}
</DTCard>
</AnimatedSection> </AnimatedSection>
{/* No Detection Card (fallback) */} {/* No Detection Card (fallback) */}
{!transponder && ( {!transponder && (
<AnimatedSection delay={0}> <AnimatedSection delay={0}>
<Surface style={styles.noDetectionCard} elevation={1}> <Surface style={styles.noDetectionCard} elevation={1}>
<Text variant="labelLarge" style={styles.noDetectionLabel}> <Text variant="labelLarge" style={styles.noDetectionLabel}>
CHIP NOT IDENTIFIED CHIP NOT IDENTIFIED
</Text> </Text>
<Divider style={[styles.divider, { backgroundColor: DTColors.modeWarning }]} /> <Divider style={[styles.divider, { backgroundColor: DTColors.modeWarning }]} />
<Text variant="bodyMedium" style={styles.noDetectionText}> <Text variant="bodyMedium" style={styles.noDetectionText}>
Unable to identify this chip type. It may be unsupported or require advanced detection. Unable to identify this chip type. It may be unsupported or require advanced detection.
</Text> </Text>
<Text variant="bodyMedium" style={styles.noDetectionText}> <Text variant="bodyMedium" style={styles.noDetectionText}>
Need help? Ask on our forum for assistance. Need help? Ask on our forum for assistance.
</Text> </Text>
<DTButton <DTButton
onPress={() => Linking.openURL(buildTrackedUrl('https://dngr.us/forum', 'unknown_chip'))} onPress={() => Linking.openURL(buildTrackedUrl('https://dngr.us/forum', 'unknown_chip'))}
> >
VISIT FORUM VISIT FORUM
</DTButton> </DTButton>
</Surface> </Surface>
</AnimatedSection> </AnimatedSection>
)} )}

View File

@@ -78,13 +78,27 @@ export function detectSpark2FromNdef(
/** /**
* Product type values from GET_VERSION byte 1 * Product type values from GET_VERSION byte 1
* Per NXP datasheets:
* - NTAG 413 DNA (NT4H1321): Type 0x04, Subtype 0x02
* - NTAG 424 DNA (NT4H2421): Type 0x04, Subtype 0x05
* - NTAG 424 DNA TT: Type 0x04, Subtype 0x04
* - DESFire EV1/EV2/EV3: Type 0x01
*/ */
const PRODUCT_TYPES = { const PRODUCT_TYPES = {
DESFIRE: 0x01, // Standard DESFire DESFIRE: 0x01, // Standard DESFire
NTAG: 0x04, // NTAG family (including NTAG 413 DNA which uses 0x04) NTAG_DNA: 0x04, // NTAG DNA family (413/424) - distinguished by subtype
NTAG_I2C: 0x05, // NTAG I2C family (can have ISO-DEP on Plus variants) NTAG_I2C: 0x05, // NTAG I2C family (can have ISO-DEP on Plus variants)
DESFIRE_LIGHT: 0x08, // DESFire Light DESFIRE_LIGHT: 0x08, // DESFire Light
NTAG_424_DNA: 0x21, // NTAG 424 DNA family } as const;
/**
* NTAG DNA subtype values from GET_VERSION byte 2
* Per NXP NT4H1321 and NT4H2421 datasheets
*/
const NTAG_DNA_SUBTYPES = {
NTAG413_DNA: 0x02, // NTAG 413 DNA
NTAG424_DNA_TT: 0x04, // NTAG 424 DNA TagTamper
NTAG424_DNA: 0x05, // NTAG 424 DNA
} as const; } as const;
/** /**
@@ -246,29 +260,31 @@ export async function detectDesfire(): Promise<DesfireDetectionResult> {
// Determine chip type based on product type and version // Determine chip type based on product type and version
let chipType: ChipType; let chipType: ChipType;
if (hwProductType === PRODUCT_TYPES.NTAG_424_DNA) { if (hwProductType === PRODUCT_TYPES.NTAG_DNA) {
// NTAG 424 DNA family uses product type 0x21 // NTAG DNA family (413/424) - distinguish by subtype per NXP datasheets
// Subtype 0x02 = TagTamper variant (424 DNA TT only) // NT4H1321 (NTAG 413 DNA): Subtype 0x02, Major 0x04
// Storage size helps differentiate: // NT4H2421 (NTAG 424 DNA): Subtype 0x05, Major 0x04
// - NTAG 413 DNA: ~160 bytes user memory (storage code <= 0x0E) // NT4H2421 TT (NTAG 424 DNA TagTamper): Subtype 0x04, Major 0x04
// - NTAG 424 DNA: ~416 bytes user memory (storage code >= 0x0F) switch (hwSubtype) {
if (hwSubtype === 0x02) { case NTAG_DNA_SUBTYPES.NTAG413_DNA:
chipType = ChipType.NTAG424_DNA_TT; console.log('[DESFire] Detected NTAG 413 DNA (product 0x04, subtype 0x02)');
} else if (hwStorageSize <= 0x0e) { chipType = ChipType.NTAG413_DNA;
// Smaller storage indicates NTAG 413 DNA (~160 bytes) break;
chipType = ChipType.NTAG413_DNA; case NTAG_DNA_SUBTYPES.NTAG424_DNA_TT:
} else { console.log('[DESFire] Detected NTAG 424 DNA TT (product 0x04, subtype 0x04)');
// Larger storage indicates NTAG 424 DNA (~416 bytes) chipType = ChipType.NTAG424_DNA_TT;
chipType = ChipType.NTAG424_DNA; break;
case NTAG_DNA_SUBTYPES.NTAG424_DNA:
console.log('[DESFire] Detected NTAG 424 DNA (product 0x04, subtype 0x05)');
chipType = ChipType.NTAG424_DNA;
break;
default:
// Unknown NTAG DNA subtype - log and fall back to 424 DNA as most common
console.warn(
`[DESFire] Unknown NTAG DNA subtype: 0x${hwSubtype.toString(16)}, major: 0x${hwMajor.toString(16)}`,
);
chipType = ChipType.NTAG424_DNA;
} }
} else if (hwProductType === PRODUCT_TYPES.NTAG && hwMajor >= 0x30) {
// NTAG 413 DNA returns product type 0x04 (like regular NTAG) but with:
// - hwMajor >= 0x30 (typically 0x30)
// - hwStorageSize 0x11 (different meaning than regular NTAG)
// - Responds to IsoDep (unlike regular NTAG 21x)
// Per NXP NT4H1321 datasheet, NTAG 413 DNA uses this response format
console.log('[DESFire] Detected NTAG 413 DNA (product type 0x04, hwMajor 0x30+)');
chipType = ChipType.NTAG413_DNA;
} else if (hwProductType === PRODUCT_TYPES.NTAG_I2C) { } else if (hwProductType === PRODUCT_TYPES.NTAG_I2C) {
// NTAG I2C family (can have ISO-DEP on Plus variants) // NTAG I2C family (can have ISO-DEP on Plus variants)
// Per NXP NT3H2111/NT3H2211 datasheet: // Per NXP NT3H2111/NT3H2211 datasheet: