304 lines
8.6 KiB
JavaScript
304 lines
8.6 KiB
JavaScript
// MAC Address Generator
|
|
|
|
import { randomElement } from './utils.js';
|
|
import { getConfig } from './config.js';
|
|
|
|
// Data cache to reduce server requests
|
|
const dataCache = new Map();
|
|
const CACHE_PREFIX = 'mac_data_cache_';
|
|
const CACHE_VERSION = 'v1';
|
|
const CACHE_EXPIRY = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
|
// Load OUI data from JSON file with caching (memory + localStorage)
|
|
async function loadOuiData() {
|
|
const filePath = 'data/macOuiData.json';
|
|
|
|
try {
|
|
// Check memory cache first
|
|
if (dataCache.has(filePath)) {
|
|
return dataCache.get(filePath);
|
|
}
|
|
|
|
// Check localStorage cache
|
|
const cacheKey = CACHE_PREFIX + filePath;
|
|
try {
|
|
const cachedData = localStorage.getItem(cacheKey);
|
|
if (cachedData) {
|
|
const parsed = JSON.parse(cachedData);
|
|
if (parsed.timestamp && (Date.now() - parsed.timestamp) < CACHE_EXPIRY) {
|
|
dataCache.set(filePath, parsed.data);
|
|
return parsed.data;
|
|
} else {
|
|
localStorage.removeItem(cacheKey);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn('localStorage cache read failed:', e);
|
|
}
|
|
|
|
// Get user configuration
|
|
const config = getConfig();
|
|
const fileName = filePath.split('/').pop();
|
|
|
|
// Build paths array based on configuration
|
|
const paths = [];
|
|
|
|
// If user has configured a custom dataBasePath, use it first
|
|
if (config.dataBasePath) {
|
|
// Ensure trailing slash
|
|
const basePath = config.dataBasePath.endsWith('/') ? config.dataBasePath : config.dataBasePath + '/';
|
|
paths.push(basePath + fileName);
|
|
}
|
|
|
|
// If autoDetectPaths is enabled (default), add automatic path detection
|
|
// This preserves the original behavior for mockaddress.com
|
|
if (config.autoDetectPaths !== false) {
|
|
const currentPath = window.location.pathname;
|
|
|
|
// Try multiple possible paths
|
|
// Priority: relative path (../data/) first, then absolute paths
|
|
paths.push(
|
|
`../data/${fileName}`, // Relative: go up one level, then into data (works for all language versions)
|
|
`/data/${fileName}`, // Absolute path from root (for Chinese version)
|
|
`data/${fileName}`, // Relative to current directory (fallback)
|
|
filePath // Original path (fallback)
|
|
);
|
|
|
|
// Add language-specific absolute paths if we're in a language subdirectory
|
|
const pathParts = currentPath.split('/').filter(p => p && p !== 'index.html' && p !== '');
|
|
if (pathParts.length >= 1 && ['en', 'ru', 'es', 'pt'].includes(pathParts[0])) {
|
|
// We're in a language subdirectory, add language-specific absolute path
|
|
const lang = pathParts[0];
|
|
paths.splice(paths.length - 2, 0, `/${lang}/data/${fileName}`); // Insert before fallback paths
|
|
}
|
|
}
|
|
|
|
let lastError = null;
|
|
for (const path of paths) {
|
|
try {
|
|
const response = await fetch(path, {
|
|
// Add cache control to help browser cache
|
|
cache: 'default'
|
|
});
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
dataCache.set(filePath, data);
|
|
|
|
// Store in localStorage
|
|
try {
|
|
const cacheData = {
|
|
data: data,
|
|
timestamp: Date.now(),
|
|
version: CACHE_VERSION
|
|
};
|
|
localStorage.setItem(cacheKey, JSON.stringify(cacheData));
|
|
} catch (e) {
|
|
console.warn('localStorage cache write failed:', e);
|
|
}
|
|
|
|
return data;
|
|
} else {
|
|
lastError = `HTTP ${response.status} for ${path}`;
|
|
}
|
|
} catch (e) {
|
|
// Record error but continue trying other paths
|
|
lastError = e.message || e.toString();
|
|
continue;
|
|
}
|
|
}
|
|
|
|
console.error(`Failed to load ${filePath}. Tried paths:`, paths, 'Last error:', lastError);
|
|
throw new Error(`Failed to load ${filePath}: ${lastError || 'All paths failed'}`);
|
|
} catch (error) {
|
|
console.error(`Error loading OUI data:`, error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Generate random byte using crypto.getRandomValues
|
|
function randomByte() {
|
|
return crypto.getRandomValues(new Uint8Array(1))[0];
|
|
}
|
|
|
|
// Convert OUI string (e.g., "00:03:93") to bytes
|
|
function ouiStringToBytes(ouiString) {
|
|
const parts = ouiString.split(':');
|
|
return new Uint8Array([
|
|
parseInt(parts[0], 16),
|
|
parseInt(parts[1], 16),
|
|
parseInt(parts[2], 16)
|
|
]);
|
|
}
|
|
|
|
// Generate MAC address
|
|
function generateMACAddress(options = {}) {
|
|
const {
|
|
vendor = 'random',
|
|
format = 'colon',
|
|
unicast = true,
|
|
laa = false
|
|
} = options;
|
|
|
|
let bytes = new Uint8Array(6);
|
|
|
|
// Generate first 3 bytes (OUI)
|
|
if (vendor !== 'random' && options.ouiDb) {
|
|
// Search in full OUI database for matching vendor
|
|
const matchingOuis = Object.keys(options.ouiDb).filter(oui => {
|
|
const vendorName = options.ouiDb[oui];
|
|
// Check if vendor name contains the search term or vice versa
|
|
return vendorName.toLowerCase().includes(vendor.toLowerCase()) ||
|
|
vendor.toLowerCase().includes(vendorName.toLowerCase().split(',')[0]);
|
|
});
|
|
|
|
if (matchingOuis.length > 0) {
|
|
// Use random OUI from matching vendors
|
|
const selectedOui = randomElement(matchingOuis);
|
|
const ouiBytes = ouiStringToBytes(selectedOui);
|
|
bytes[0] = ouiBytes[0];
|
|
bytes[1] = ouiBytes[1];
|
|
bytes[2] = ouiBytes[2];
|
|
} else {
|
|
// Vendor not found, generate random
|
|
crypto.getRandomValues(bytes.subarray(0, 3));
|
|
}
|
|
} else {
|
|
// Completely random
|
|
crypto.getRandomValues(bytes.subarray(0, 3));
|
|
}
|
|
|
|
// Generate last 3 bytes (device identifier)
|
|
crypto.getRandomValues(bytes.subarray(3));
|
|
|
|
// Apply unicast bit (LSB of first byte = 0 for unicast, 1 for multicast)
|
|
if (unicast) {
|
|
bytes[0] &= 0xFE; // Clear bit 0
|
|
} else {
|
|
bytes[0] |= 0x01; // Set bit 0
|
|
}
|
|
|
|
// Apply LAA bit (bit 1 of first byte = 1 for locally administered, 0 for globally unique)
|
|
if (laa) {
|
|
bytes[0] |= 0x02; // Set bit 1
|
|
} else {
|
|
bytes[0] &= 0xFD; // Clear bit 1
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
// Format MAC address
|
|
function formatMACAddress(bytes, format = 'colon') {
|
|
const hex = Array.from(bytes).map(b =>
|
|
b.toString(16).padStart(2, '0').toUpperCase()
|
|
);
|
|
|
|
switch (format) {
|
|
case 'colon':
|
|
return hex.join(':');
|
|
case 'hyphen':
|
|
return hex.join('-');
|
|
case 'dot':
|
|
return `${hex[0]}${hex[1]}.${hex[2]}${hex[3]}.${hex[4]}${hex[5]}`;
|
|
case 'none':
|
|
return hex.join('');
|
|
case 'space':
|
|
return hex.join(' ');
|
|
default:
|
|
return hex.join(':');
|
|
}
|
|
}
|
|
|
|
// Convert MAC to IPv6 Link-Local (EUI-64)
|
|
function macToIPv6(bytes) {
|
|
const b = [...bytes];
|
|
// Flip U/L bit (bit 7 of first byte)
|
|
b[0] ^= 0x02;
|
|
|
|
// Insert FFFE in the middle
|
|
const eui64 = [b[0], b[1], b[2], 0xFF, 0xFE, b[3], b[4], b[5]];
|
|
|
|
// Convert to IPv6 format
|
|
const groups = [];
|
|
for (let i = 0; i < 8; i += 2) {
|
|
const group = ((eui64[i] << 8) | eui64[i + 1]).toString(16);
|
|
groups.push(group);
|
|
}
|
|
|
|
return 'fe80::' + groups.join(':');
|
|
}
|
|
|
|
// Identify vendor from MAC address
|
|
function identifyVendor(bytes, ouiDb) {
|
|
if (!ouiDb) return null;
|
|
|
|
const ouiString = Array.from(bytes.slice(0, 3))
|
|
.map(b => b.toString(16).padStart(2, '0').toUpperCase())
|
|
.join(':');
|
|
|
|
return ouiDb[ouiString] || null;
|
|
}
|
|
|
|
// Generate MAC address with all options
|
|
export async function generateMAC(options = {}) {
|
|
try {
|
|
const {
|
|
count = 1,
|
|
vendor = 'random',
|
|
format = 'colon',
|
|
unicast = true,
|
|
laa = false,
|
|
showIPv6 = false
|
|
} = options;
|
|
|
|
// 限制最大生成数量为888
|
|
const actualCount = Math.min(888, Math.max(1, count));
|
|
|
|
// Load OUI data
|
|
const ouiDb = await loadOuiData();
|
|
|
|
const results = [];
|
|
|
|
for (let i = 0; i < actualCount; i++) {
|
|
const bytes = generateMACAddress({
|
|
vendor,
|
|
format,
|
|
unicast,
|
|
laa,
|
|
ouiDb
|
|
});
|
|
|
|
const mac = formatMACAddress(bytes, format);
|
|
const vendorName = identifyVendor(bytes, ouiDb);
|
|
const ipv6 = showIPv6 ? macToIPv6(bytes) : null;
|
|
|
|
results.push({
|
|
mac,
|
|
vendor: vendorName,
|
|
ipv6,
|
|
bytes: Array.from(bytes),
|
|
format,
|
|
unicast,
|
|
laa
|
|
});
|
|
}
|
|
|
|
return results;
|
|
} catch (error) {
|
|
console.error('Error generating MAC address:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Get available vendors from OUI database
|
|
export async function getAvailableVendors() {
|
|
try {
|
|
const ouiDb = await loadOuiData();
|
|
const vendors = [...new Set(Object.values(ouiDb))].sort();
|
|
return vendors;
|
|
} catch (error) {
|
|
console.error('Error getting vendors:', error);
|
|
return [];
|
|
}
|
|
}
|