欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

实现龙卷风收音机客户端电台数据解密算法 算法F# 

程序员文章站 2022-07-15 08:45:05
...
最新说明:自7月份始,文章公布后,Cradio官方更新了加密算法,故以下实现只对7月份之前的版本有效.关于最新的电台数据,目前只能通过js的方式获取,而相关的除电台名称,地址之外信息还需另外搜集和整理.在此,请诸位自行决定.(再次声明:js获取数据方式因可能对Cradio官方服务器产生不良影响,在此不再公布.)
以下是解密算法的java实现,已通过验证:
public class Test {

    public static void main(String[] args) {
        RadioParser parser = new RadioParser("f:/data.idp", "f:/data.dat");
        for(RadioSegement segement : parser) {
            System.out.println(segement.getRadioInfo());
            System.out.println("\n\n");
        }
    }
}

public class RadioSegement {

    private int startAddr;
    private int endAddr;

    private String info;

    private RadioInfo radioInfo;

    public RadioSegement(int startAddr, int endAddr) {
        this.startAddr = startAddr;
        this.endAddr = endAddr;
    }

    public int getStartAddr() {
        return startAddr;
    }

    public int getEndAddr() {
        return endAddr;
    }

    public int length() {
        return endAddr - startAddr;
    }

    void setInfo(String info) {
        this.info = info;
    }

    public String getInfo() {
        return info;
    }

    public RadioInfo getRadioInfo() {
        if(radioInfo == null) {
            radioInfo = new RadioInfo(this);
        }
        return radioInfo;
    }

    public String toString() {
        return "startAddr: " + startAddr + ", endAddr: " + endAddr + ", info: " + info;
    }
}

public class RadioInfo {

    private String[] data;

    private static int index = 0;
    private final static int INDEX_NAME             = index++;
    private final static int INDEX_TYPE             = index++;
    private final static int INDEX_COUNTRY          = index++;
    private final static int INDEX_PROVINCE         = index++;
    private final static int INDEX_CITY             = index++;
    private final static int INDEX_LANGUAGE         = index++;
    private final static int INDEX_FORMAT           = index++;
    private final static int INDEX_VALIDITY         = index++;
    private final static int INDEX_SPEED            = index++;
    private final static int INDEX_FREQUENCY        = index++;
    static {
        index++;
    }
    private final static int INDEX_HOME_URL         = index++;
    private final static int INDEX_PROGRAMME_URL    = index++;
    private final static int INDEX_BROADCAST_URL    = index++;

    RadioInfo(RadioSegement segement) {
        data = segement.getInfo().split("\t", -1);
        if(data.length != 14) {
            return;
        }
    }

    public String getName() {
        return data[INDEX_NAME];
    }

    public String getType() {
        return data[INDEX_TYPE];
    }

    public String getCountry() {
        return data[INDEX_COUNTRY];
    }

    public String getProvince() {
        return data[INDEX_PROVINCE];
    }

    public String getCity() {
        return data[INDEX_CITY];
    }

    public String getLanguage() {
        return data[INDEX_LANGUAGE];
    }

    public String getFormat() {
        return data[INDEX_FORMAT];
    }

    public String getValidity() {
        return data[INDEX_VALIDITY];
    }

    public String getSpeed() {
        return data[INDEX_SPEED];
    }

    public String getFrequency() {
        return data[INDEX_FREQUENCY];
    }

    public String getHomeUrl() {
        return data[INDEX_HOME_URL];
    }

    public String getProgrammeUrl() {
        return data[INDEX_PROGRAMME_URL];
    }

    public String getBroadcastUrl() {
        return data[INDEX_BROADCAST_URL];
    }

    public String toString() {
        return new StringBuilder()
            .append("\n名称: ").append(getName())
            .append("\n类型: ").append(getType())
            .append("\n国家: ").append(getCountry())
            .append("\n省份: ").append(getProvince())
            .append("\n城市: ").append(getCity())
            .append("\n语言: ").append(getLanguage())
            .append("\n格式: ").append(getFormat())
            .append("\n有效性: ").append(getValidity())
            .append("\n速率: ").append(getSpeed())
            .append("\n频率: ").append(getFrequency())
            .append("\n主页: ").append(getHomeUrl())
            .append("\n节目表: ").append(getProgrammeUrl())
            .append("\n广播地址: ").append(getBroadcastUrl())
            .toString();
    }
}

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import com.bao.util.io.FileUtil;
import com.bao.util.lang.ByteUtil;

public class RadioParser implements Iterable<RadioSegement>{

    /**
     * 解码字符
     */
    private final static char[] xorChar = "jin_cr".toCharArray();

    /**
     * 地址文件
     */
    private File addrFile;

    /**
     * 数据文件
     */
    private File dataFile;

    /**
     * 数据段
     */
    private List<RadioSegement> segements = null;

    public RadioParser(String addrFilename, String dataFilename) {
        this.addrFile = new File(addrFilename);
        this.dataFile = new File(dataFilename);
    }

    @Override
    public Iterator<RadioSegement> iterator() {
        if(segements == null) {
            initAddrList();
            initRadioData();
        }
        return segements.iterator();
    }

    /**
     * 解析地址
     */
    private void initAddrList() {
        segements = new LinkedList<RadioSegement>();
        BufferedInputStream bis = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(addrFile));
            int maxLen = (int)dataFile.length();
            byte[] bys = new byte[4];
            for(int start = 0; bis.read(bys) != -1; ) {
                int end = ByteUtil.bytes2IntLE(bys);
                if(end == 0) {
                    continue;
                }
                if(end > maxLen) {
                    break;
                }
                segements.add(new RadioSegement(start, end));
                start = end;
            }
        }catch(IOException e) {
            e.printStackTrace();
        }finally{
            try {
                FileUtil.closeIO(bis);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析数据段
     */
    private void initRadioData() {
        BufferedInputStream bis = null;
        try {
            bis = new BufferedInputStream(new FileInputStream(dataFile));
            for(RadioSegement segement : segements) {
                byte[] bys = new byte[segement.length()];
                bis.read(bys);
                segement.setInfo(parseBytes(bys));
            }
        }catch(IOException e) {
            e.printStackTrace();
        }finally{
            try {
                FileUtil.closeIO(bis);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 解码
     * @param bys
     * @return
     * @throws UnsupportedEncodingException
     */
    private static String parseBytes(byte[] bys) throws UnsupportedEncodingException {
        if(bys == null) {
            return null;
        }
        if(bys.length == 0) {
            return "";
        }
        int offset = 0;
        while(offset < bys.length && bys[offset++] != 0x09);
        for(int i = offset, k = 0; i < bys.length; i++, k++) {
            bys[i] = (byte)(bys[i] ^ xorChar[k % xorChar.length]);
        }
        return new String(bys, offset, bys.length - offset - 2, "gbk");
    }
}

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * 读取文件工具类
 * 
 * @author bao110908
 * @since 2008-12-14
 */
public class FileUtil {
    
    /**
     * 行分隔符
     */
    public final static String LINE_SEPARATOR = System.getProperty("line.separator");
    
    /**
     * 建一个目录
     * @param file
     */
    public static boolean createDirectory(File file) {
        if(file.exists()) {
            return true;
        }
        return file.mkdirs();        
    }
    
    public static boolean createDirectory(String dirname) {
        return createDirectory(new File(dirname));
    }
    
    /**
     * 从文件名中读取文件
     * @param filename
     * @return
     * @throws IOException
     */
    public static String readFile2String(String filename) throws IOException {
        return readFile2String(new File(filename));
    }
    
    /**
     * 从 File 对象中读取文件
     * @param file
     * @return
     * @throws IOException
     */
    public static String readFile2String(File file) throws IOException {
        if((file == null) || !file.exists() || file.isDirectory()) {
            return null;
        }
        return readInputStream2String(new FileInputStream(file));
    }
    
    /**
     * 使用系统默认字符集读取二进制流
     * @param is
     * @return
     * @throws IOException
     */
    public static String readInputStream2String(InputStream is)
            throws IOException {
        return readInputStream2String(is, Charset.defaultCharset().name());
    }
    
    /**
     * 使用指定编码读取二进制流
     * @param is
     * @param charset
     * @return
     * @throws IOException
     */
    public static String readInputStream2String(InputStream is, String charset)
            throws IOException {
        BufferedReader br = null;
        StringBuilder sb = new StringBuilder();
        try {
            br = new BufferedReader(new InputStreamReader(is, charset));
            for(String str = null; (str = br.readLine()) != null; ) {
                sb.append(str).append(LINE_SEPARATOR);
            }
        } finally {
            closeIO(br);
        }
        return sb.toString().trim();
    }
    
    public static List<String> readFile2List(String filename)
            throws IOException {
        return readFile2List(new File(filename));
    }
    
    /**
     * 将文件读取成为 List,List 中的每一个元素是文件中的一行字符串 
     * @param file
     * @return
     * @throws IOException
     */
    public static List<String> readFile2List(File file) throws IOException {
        if((file == null) || !file.exists() || file.isDirectory()) {
            return null;
        }
        BufferedReader br = null;
        List<String> list = new ArrayList<String>();
        try {
            br = new BufferedReader(new FileReader(file));
            for(String str = null; (str = br.readLine()) != null; ) {
                list.add(str);
            }
        } finally {
            closeIO(br);
        }
        return list;
    }
    
    public static void writeString2File(String str, String filename)
            throws IOException {
        writeString2File(str, new File(filename));
    }
    
    public static void writeString2File(String str, File file)
            throws IOException {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter(file));
            bw.write(str);
        } finally {
            closeIO(bw);
        }
    }
    
    public static void writeList2File(List<String> list, String filename)
            throws IOException {
        writeList2File(list, new File(filename), LINE_SEPARATOR);
    }
    
    public static void writeList2File(List<String> list, File file)
            throws IOException {
        writeList2File(list, file, LINE_SEPARATOR);
    }
    
    public static void writeList2File(List<String> list, String filename,
            String lineSeparator) throws IOException {
        writeList2File(list, new File(filename), lineSeparator);
    }
    
    /**
     * 以 List 中的每一个元素作为写入文件中的一行
     * @param list
     *      元素集
     * @param file
     *      文件
     * @param lineSeparator
     *      每个元素写入文件时的分隔符
     * @throws IOException
     */
    public static void writeList2File(List<String> list, File file,
            String lineSeparator) throws IOException {
        StringBuffer sb = new StringBuffer();
        for(int i = 0, k = list.size(); i < k; i++) {
            if(i > 0) {
                sb.append(lineSeparator);
            }
            sb.append(list.get(i));
        }
        writeString2File(sb.toString(), file);
    }
    
    /**
     * 关闭 IO 流
     * @param io
     * @throws IOException
     */
    public static void closeIO(Closeable io) throws IOException {
        if(io != null) {
            io.close();
        }
    }
}

/**
 * 字节工具类
 * @author bao110908
 * @since 2009-03-07
 */
public class ByteUtil {

    private final static char[] HEX = "0123456789abcdef".toCharArray();     
    
    /**
     * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,每个字节间采用
     * 一个空格分隔<br />
     * 采用实现较为高效的 bytes2Hex 方法
     * @param bys       需要转换成 16 进制的字节数组
     * @return
     * @deprecated
     */
    public static String bytes2Hex1(byte[] bys) {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < bys.length; i++) {
            if(i > 0) {
                sb.append(" ");
            }
            sb.append(HEX[bys[i] >> 4 & 0xf]);
            sb.append(HEX[bys[i] & 0xf]);
        }
        return sb.toString();
    }
    
    /**
     * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,每个字节间采用
     * 一个空格分隔
     * @param bys       需要转换成 16 进制的字节数组
     * @return
     */
    public static String bytes2HexSpace(byte[] bys) {
        char[] chs = new char[bys.length * 2 + bys.length - 1];
        for(int i = 0, offset = 0; i < bys.length; i++) {
            if(i > 0) {
                chs[offset++] = ' ';
            }
            chs[offset++] = HEX[bys[i] >> 4 & 0xf];
            chs[offset++] = HEX[bys[i] & 0xf];
        }
        return new String(chs);
    }
    
    /**
     * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示
     * 
     * @param bys       需要转换成 16 进制的字节数组
     * @return
     */
    public static String bytes2Hex(byte[] bys) {
        char[] chs = new char[bys.length * 2];
        for(int i = 0, offset = 0; i < bys.length; i++) {
            chs[offset++] = HEX[bys[i] >> 4 & 0xf];
            chs[offset++] = HEX[bys[i] & 0xf];
        }
        return new String(chs);
    }
    
    /**
     * 将字节数组转成 16 进制的字符串来表示,每个字节采用两个字符表表示,字节间没有分隔
     * 符。<br />
     * 采用实现较为高效的 bytes2Hex 方法
     * @param bys       需要转换成 16 进制的字节数组
     * @return
     * @deprecated
     */
    public static String bytes2Hex2(byte[] bys) {
        StringBuilder sb = new StringBuilder();
        for(int i = 0; i < bys.length; i++) {
            sb.append(HEX[bys[i] >> 4 & 0xf]);
            sb.append(HEX[bys[i] & 0xf]);
        }
        return sb.toString();
    }
    
    public static byte[] int2BytesBE(int num) {
        byte[] bys = new byte[Integer.SIZE / Byte.SIZE];
        for(int i = 0, k = bys.length; i < k; i++) {
            bys[i] = (byte)(num >>> ((k - 1 - i) * Byte.SIZE) & 0xff);
        }
        return bys;
    }
    
    public static byte[] int2BytesLE(int num) {
        return int2BytesBE(Integer.reverseBytes(num));
    }
    
    /**
     * 采用 Big-Endian 方式将 long 数据转为 byte 数组
     * 
     * @param num
     * @return  转为 Big-Endian 方式的 byte 数组 
     */
    public static byte[] long2BytesBE(long num) {
        byte[] bys = new byte[Long.SIZE / Byte.SIZE];
        for(int i = 0, k = bys.length; i < k; i++) {
            bys[i] = (byte)(num >>> ((k - 1 - i) * Byte.SIZE) & 0xff);
        }
        return bys;
    }
    
    /**
     * 采用 Little-Endian 方式将 long 数据转为 byte 数组
     * 
     * @param num
     * @return  转为 Little-Endian 方式的 byte 数组 
     */
    public static byte[] long2BytesLE(long num) {        
        return long2BytesBE(Long.reverseBytes(num));
    }
    
    /**
     * 将 Little-Endian 的字节数组转为 int 类型的数据<br />
     * Little-Endian 表示高位字节在低位索引中
     * @param bys       字节数组
     * @param start     需要转换的开始索引位数
     * @param len       需要转换的字节数量
     * @return  指定开始位置和长度以 LE 方式表示的 int 数值
     */
    public static int bytes2IntLE(byte[] bys, int start, int len) {
        return bytes2Int(bys, start, len, false);
    }
    
    public static int bytes2IntLE(byte[] bys) {
        return bytes2Int(bys, 0, bys.length, false);
    }

    /**
     * 将 Big-Endian 的字节数组转为 int 类型的数据<br />
     * Big-Endian 表示高位字节在高位索引中
     * @param bys       字节数组
     * @param start     需要转换的开始索引位数
     * @param len       需要转换的字节数量
     * @return  指定开始位置和长度以 BE 方式表示的 int 数值
     */
    public static int bytes2IntBE(byte[] bys, int start, int len) {
        return bytes2Int(bys, start, len, true);
    }
    
    private static int bytes2Int(byte[] bys, int start, int len, 
            boolean isBigEndian) {
        int n = 0;
        for(int i = start, k = start + len % (Integer.SIZE / Byte.SIZE + 1); i < k; i++) {
            n |= (bys[i] & 0xff) << ((isBigEndian ? (k - i - 1) : i) * Byte.SIZE);
        }
        return n;
    }
    
    /**
     * 将 Little-Endian 的字节数组转为 long 类型的数据<br />
     * Little-Endian 表示高位字节在低位索引中
     * @param bys       字节数组
     * @param start     需要转换的开始索引位数
     * @param len       需要转换的字节数量
     * @return  指定开始位置和长度以 LE 方式表示的 long 数值
     */
    public static long bytes2LongLE(byte[] bys, int start, int len) {
        return bytes2Long(bys, start, len, false);
    }
    
    public static long bytes2LongLE(byte[] bys) {
        return bytes2Long(bys, 0, bys.length, false);
    }
    
    /**
     * 将 Big-Endian 的字节数组转为 long 类型的数据<br />
     * Big-Endian 表示高位字节在高位索引中
     * @param bys       字节数组
     * @param start     需要转换的开始索引位数
     * @param len       需要转换的字节数量
     * @return  指定开始位置和长度以 BE 方式表示的 long 数值
     */
    public static long bytes2LongBE(byte[] bys, int start, int len) {
        return bytes2Long(bys, start, len, true);
    }
    
    public static long bytes2LongBE(byte[] bys) {
        return bytes2Long(bys, 0, bys.length, true);
    }
    
    private static long bytes2Long(byte[] bys, int start, int len, 
            boolean isBigEndian) {
        long n = 0L;
        for(int i = start, k = start + len % (Long.SIZE / Byte.SIZE + 1); i < k; i++) {
            n |= (bys[i] & 0xffL) << ((isBigEndian ? (k - i - 1) : i) * Byte.SIZE);
        }
        return n;
    }
}
相关标签: 算法 F#