论坛首页 Java企业应用论坛

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

浏览 4686 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (1) :: 隐藏帖 (0)
作者 正文
   发表时间:2009-07-01   最后修改:2009-12-09
最新说明:自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;
    }
}
   发表时间:2009-07-02  
楼主能不能多一点文字说明一下,毕竟是算法
0 请登录后投票
   发表时间:2009-07-02  
zhiblin 写道
楼主能不能多一点文字说明一下,毕竟是算法

算法的说明和描述请参看上一篇文章.这里不再重复说明!
0 请登录后投票
   发表时间:2009-09-01  
算法很好,但是看不懂,说明太少了
0 请登录后投票
   发表时间:2009-09-01  
很好的软件  上班听听  就是现在的广播广告太多了.. 评书类节目太少了  各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢
0 请登录后投票
   发表时间:2009-09-03  
fjlyxx 写道
很好的软件  上班听听  就是现在的广播广告太多了.. 评书类节目太少了  各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢

电台倒是全部都有了(就是fifm和cradio上面的全部电台数据),但不可能全部都收听过来,如果你需要的话,我可以把你需要的分类进行筛选一下发给你.呵呵~
0 请登录后投票
   发表时间:2009-09-03  
fjlyxx 写道
很好的软件  上班听听  就是现在的广播广告太多了.. 评书类节目太少了  各位GGJJ推荐几个评书类 新闻类 专题类的节目 谢谢

ps下,我现在一般听怀旧金曲,都市流行和中国之声,经济之声,还有几个地方电台.关于musicRadio都很少听了.确实,广告太多了.而且还太频繁了,半个小时一次,听着不爽.
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics