转轮机加密的C/C++实现

前言

    转轮机的原理及故事已经“家喻户晓”,因此不再赘述。
    网上能找到的代码实现采用的是比对轮子左值和右值查找下标的方法。这里给出一种效率更高的实现,将轮子左/右值和数组下标对应,然后先对左右两侧的映射关系进行反映射,实现加密和解密的直接取值,不需要每次查找,时间复杂度从O(n 2 ^2 2)降低到O(n)。
    由于数组下标是从0开始,而轮子上的序号是从1开始,因此代码中对轮子的初始值作减一操作以对应反映射数组的下标,方便计算和取值。
    然而,为了保证数组下标不越界,代码中用了很多“取余运算”。如果大家有减少取余运算的思路,还请在评论分享一下~

C++实现

#include <iostream>
#include <cstring>

using std::dec;
using std::cout;
using std::cin;
using std::endl;

#define WHEEL_LOOP_SIZE             (26)
#define VALUE_F0_SLOW               (23) //24 - 1
#define VALUE_F0_MID                (25) //26 - 1
#define VALUE_F0_FAST               (0)  //1 - 1
#define CHECK_ALPHABET(ch)          ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z'))

typedef char          s8;
typedef unsigned char u8;
typedef unsigned int  u32;

/* 转轮机三个轮子的初始状态:
** 慢轮左边:{ 24,25,26,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 }
** 慢轮右边:{ 21,3,15,1,19,10,14,26,20,8,16,7,22,4,11,5,17,9,12,23,18,2,25,6,24,13 }
** 中轮左边:{ 26,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 }
** 中轮右边:{ 20,1,6,4,15,3,14,12,23,5,16,2,22,19,11,18,25,24,13,7,10,8,21,9,26,17 }
** 快轮左边:{ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 }
** 快轮右边:{ 8,18,26,17,20,22,10,3,13,11,4,23,5,24,9,12,25,16,19,6,15,21,2,7,1,14 }
**
** 为了轮子序号值于数组下标对应,对上述序号值作“-1”操作
*/
const u8 abSlowR[WHEEL_LOOP_SIZE] = {
    
     20,2,14,0,18,9,13,25,19,7,15,6,21,3,10,4,16,8,11,22,17,1,24,5,23,12 };
const u8 abMidR[WHEEL_LOOP_SIZE]  = {
    
     19,0,5,3,14,2,13,11,22,4,15,1,21,18,10,17,24,23,12,6,9,7,20,8,25,16 };
const u8 abFastR[WHEEL_LOOP_SIZE] = {
    
     7,17,25,16,19,21,9,2,12,10,3,22,4,23,8,11,24,15,18,5,14,20,1,6,0,13 };

/* 为了便于查找,对每个轮子进行反映射:
** 设轮子左边下标为x,右边为y;
** 设轮子左边的映射关系为f(x),右边为g(y);
** 那么对于给定x,只要找到y满足条件f(x)=g(y),则为轮子右边的下标,作为下一个轮子的x值。
** 则轮子映射关系为:明文字符 -> x -> f(x) == g(y) -> y -> 密文字符。
** 因此,y = g-1(g(y)) = g-1(f(x))。
**
** 对于轮子的左边,f(x)符合线性规律。有f(x) = (f(0) + x) % 26,因此x = (f(x) + 26 - f(0)) % 26。
** 其中,f1(0) = (23 + 26 - idxSlow) % 26;
**       f2(0) = (25 + 26 - idxMid) % 26;
**       f3(0) = (26 - idxFast) % 26.
**
** 对于轮子的右边,g(y)符合离散规律。
** 轮子转动后,g(y)会变化,具体体现为:g(y) = abOriginR[y + 26 - idxRotate],y = (abReverseR[g(y)] + idxRotate) % 26.
*/
const u8 abRvrsSlowR[WHEEL_LOOP_SIZE] = {
    
     3,21,1,13,15,23,11,9,17,5,14,18,25,6,2,10,16,20,4,8,0,12,19,24,22,7 };
const u8 abRvrsMidR [WHEEL_LOOP_SIZE] = {
    
     1,11,5,3,9,2,19,21,23,20,14,7,18,6,4,10,25,15,13,0,22,12,8,17,16,24 };
const u8 abRvrsFastR[WHEEL_LOOP_SIZE] = {
    
     24,22,7,10,12,19,23,0,14,6,9,15,8,25,20,17,3,1,18,4,21,5,11,13,16,2 };

class WheelCipher
{
    
    
    //WARNING: size only support 32bit.
private:
    u8 _idxSlow;	//慢轮转动的圈数
    u8 _idxMid;		//中轮转动的圈数
    u8 _idxFast;	//快轮转动的圈数
    u8 _f0Slow;		//慢轮当前初始值
    u8 _f0Mid;		//中轮当前初始值
    u8 _f0Fast;		//快轮当前初始值
    void reset(void);
    void setWheelState(s8 *str, u32 &strSize);
    void rotate(void);
    void reverse(void);
    void plainToCipher(s8 &ch);
    void cipherToPlain(s8 &ch);
public:
    WheelCipher();
    void encrypt(s8 *str);
    void decode(s8 *str);
    ~WheelCipher();
};

WheelCipher::WheelCipher()
{
    
    
    reset();
}

WheelCipher::~WheelCipher()
{
    
    

}

void WheelCipher::reset(void)
{
    
    
    _idxSlow = 0;
    _idxMid  = 0;
    _idxFast = 0;
    _f0Slow  = VALUE_F0_SLOW;
    _f0Mid   = VALUE_F0_MID;
    _f0Fast  = VALUE_F0_FAST;
}

void WheelCipher::plainToCipher(s8 &ch)
{
    
    
    s8 offsetCh;
    u8 x, f0, fx, y;
    
    offsetCh = (ch < 'a')?'A':'a';
    /* encrypt: input -> x -> f(x) -> g(y) -> y -> next wheel / output */
    //slow wheel
    x  = ch - offsetCh;
    fx = (_f0Slow + x) % WHEEL_LOOP_SIZE;
    y  = (abRvrsSlowR[fx] + _idxSlow) % WHEEL_LOOP_SIZE; //g(y) = f(x)
    //middle wheel
    fx = (_f0Mid + y) % WHEEL_LOOP_SIZE; //x = y
    y  = (abRvrsMidR[fx] + _idxMid) % WHEEL_LOOP_SIZE; //g(y) = f(x)
    //fast wheel
    fx = (_f0Fast + y) % WHEEL_LOOP_SIZE; //x = y
    y  = (abRvrsFastR[fx] + _idxFast) % WHEEL_LOOP_SIZE; //g(y) = f(x)
    
    ch = y + offsetCh;
}

void WheelCipher::cipherToPlain(s8 &ch)
{
    
    
    s8 offsetCh;
    u8 x, f0, gy, y;

    offsetCh = (ch < 'a')?'A':'a';
    /* decode: input -> y -> g(y) -> f(x) -> x -> previous wheel / output */
    /* fast wheel */
    y  = ch - offsetCh;
    gy = abFastR[(y + WHEEL_LOOP_SIZE - _idxFast) % WHEEL_LOOP_SIZE];
    //f(x) = g(y)
    x  = (gy + WHEEL_LOOP_SIZE - _f0Fast) % WHEEL_LOOP_SIZE; 
    /* middle wheel */
    //y = x
    gy =  abMidR[(x + WHEEL_LOOP_SIZE - _idxMid) % WHEEL_LOOP_SIZE];
    //f(x) = g(y)
    x  = (gy + WHEEL_LOOP_SIZE - _f0Mid) % WHEEL_LOOP_SIZE;
    /* slow wheel */
    //y = x
    gy = abSlowR[(x + WHEEL_LOOP_SIZE - _idxSlow) % WHEEL_LOOP_SIZE];
    //f(x) = g(y)
    x  = (gy + WHEEL_LOOP_SIZE - _f0Slow) % WHEEL_LOOP_SIZE;
    
    ch = x + offsetCh;
}

void WheelCipher::setWheelState(s8 *str, u32 &strSize)
{
    
    
    u32 dwRotateCnt(0);

    for(u32 i(0); i < strSize; i++){
    
    
        dwRotateCnt += CHECK_ALPHABET(str[i]);
    }

    _idxFast = dwRotateCnt % WHEEL_LOOP_SIZE;
    dwRotateCnt /= WHEEL_LOOP_SIZE;
    _idxMid = dwRotateCnt % WHEEL_LOOP_SIZE;
    dwRotateCnt /= WHEEL_LOOP_SIZE;
    _idxSlow = dwRotateCnt % WHEEL_LOOP_SIZE;

    _f0Fast  = (VALUE_F0_FAST + WHEEL_LOOP_SIZE - _idxFast) % WHEEL_LOOP_SIZE;
    _f0Mid   = (VALUE_F0_MID  + WHEEL_LOOP_SIZE - _idxMid ) % WHEEL_LOOP_SIZE;
    _f0Slow  = (VALUE_F0_SLOW + WHEEL_LOOP_SIZE - _idxSlow) % WHEEL_LOOP_SIZE;
}

void WheelCipher::rotate(void)
{
    
    
    _idxFast = (_idxFast + 1) % WHEEL_LOOP_SIZE;
    _f0Fast  = (_f0Fast + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
    if(!_idxFast){
    
    
        _idxMid = (_idxMid + 1) % WHEEL_LOOP_SIZE;
        _f0Mid  = (_f0Mid + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
        if(!_idxMid){
    
    
            _idxSlow = (_idxSlow + 1) % WHEEL_LOOP_SIZE;
            _f0Slow  = (_f0Slow + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
        }
    }
}

void WheelCipher::reverse(void)
{
    
    
    _idxFast = (_idxFast + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
    _f0Fast  = (_f0Fast + 1) % WHEEL_LOOP_SIZE;
    if(_idxFast == WHEEL_LOOP_SIZE - 1){
    
    
        _idxMid = (_idxMid + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
        _f0Mid  = (_f0Mid + 1) % WHEEL_LOOP_SIZE;
        if(_idxMid == WHEEL_LOOP_SIZE - 1){
    
    
            _idxSlow = (_idxSlow + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
            _f0Slow  = (_f0Slow + 1) % WHEEL_LOOP_SIZE;
        }
    }
}

void WheelCipher::encrypt(s8 *str)
{
    
    
    u32 dwStrSize(strlen(str));
    reset();
    for(u32 i(0); i < dwStrSize; i++){
    
    
        if(CHECK_ALPHABET(str[i])){
    
    
            // cout<<"wheel("<<(u32)_idxSlow<<','<<(u32)_idxMid<<','<<(u32)_idxFast<<") ";
            // cout<<"f0("<<(u32)_f0Slow<<','<<(u32)_f0Mid<<','<<(u32)_f0Fast<<") ";
            // cout<<str[i]<<" -> ";
            plainToCipher(str[i]);
            // cout<<str[i]<<endl;
            rotate();
        }
    }
}

void WheelCipher::decode(s8 *str)
{
    
    
    u32 dwStrSize(strlen(str));

    reset();
    setWheelState(str, dwStrSize);
    for(u32 i(dwStrSize); i; ){
    
    
        i--;
        if(CHECK_ALPHABET(str[i])){
    
    
            reverse();
            // cout<<"wheel("<<(u32)_idxSlow<<','<<(u32)_idxMid<<','<<(u32)_idxFast<<") ";
            // cout<<"f0("<<(u32)_f0Slow<<','<<(u32)_f0Mid<<','<<(u32)_f0Fast<<") ";
            // cout<<str[i]<<" -> ";
            cipherToPlain(str[i]);
            // cout<<str[i]<<endl;
        }
    }
}

int WheelCipherTest(void)
{
    
    
    s8 str[4096] = {
    
    0};
    WheelCipher objWheelCipher;

    cout<<"please input plain text:"<<endl;
    memcpy(str, "This is a wheel cipher test.", sizeof("This is a wheel cipher test."));
    cout<<str<<endl;
    // cin>>str;
    objWheelCipher.encrypt(str);
    cout<<"encrpyt result:"<<endl;
    cout<<str<<endl;
    objWheelCipher.decode(str);
    cout<<"decode result:"<<endl;
    cout<<str<<endl;

    return 0;
}

int main(void)
{
    
    
    return WheelCipherTest();
}

C实现

#include <stdio.h>
#include <string.h>

#define WHEEL_LOOP_SIZE             (26)
#define VALUE_F0_SLOW               (23) //24 - 1
#define VALUE_F0_MID                (25) //26 - 1
#define VALUE_F0_FAST               (0)  //1 - 1
#define CHECK_ALPHABET(ch)          ((ch>='A' && ch<='Z') || (ch>='a' && ch<='z'))
#define LOG                         printf

typedef char          s8;
typedef unsigned char u8;
typedef unsigned int  u32;

/* 转轮机三个轮子的初始状态:
** 慢轮左边:{ 24,25,26,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23 }
** 慢轮右边:{ 21,3,15,1,19,10,14,26,20,8,16,7,22,4,11,5,17,9,12,23,18,2,25,6,24,13 }
** 中轮左边:{ 26,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25 }
** 中轮右边:{ 20,1,6,4,15,3,14,12,23,5,16,2,22,19,11,18,25,24,13,7,10,8,21,9,26,17 }
** 快轮左边:{ 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26 }
** 快轮右边:{ 8,18,26,17,20,22,10,3,13,11,4,23,5,24,9,12,25,16,19,6,15,21,2,7,1,14 }
**
** 为了轮子序号值于数组下标对应,对上述序号值作“-1”操作
*/
const u8 abSlowR[WHEEL_LOOP_SIZE] = {
    
     20,2,14,0,18,9,13,25,19,7,15,6,21,3,10,4,16,8,11,22,17,1,24,5,23,12};
const u8 abMidR[WHEEL_LOOP_SIZE]  = {
    
     19,0,5,3,14,2,13,11,22,4,15,1,21,18,10,17,24,23,12,6,9,7,20,8,25,16};
const u8 abFastR[WHEEL_LOOP_SIZE] = {
    
     7,17,25,16,19,21,9,2,12,10,3,22,4,23,8,11,24,15,18,5,14,20,1,6,0,13};

/* 为了便于查找,对每个轮子进行反映射:
** 设轮子左边下标为x,右边为y;
** 设轮子左边的映射关系为f(x),右边为g(y);
** 那么对于给定x,只要找到y满足条件f(x)=g(y),则为轮子右边的下标,作为下一个轮子的x值。
** 因此,y = g-1(g(y)) = g-1(f(x))。
**
** 对于轮子的左边,f(x)符合线性规律。有f(x) = (f(0) + x) % 26,因此x = (f(x) + 26 - f(0)) % 26。
** 其中,f1(0) = (23 + 26 - idxSlow) % 26;
**       f2(0) = (25 + 26 - idxMid) % 26;
**       f3(0) = (26 - idxFast) % 26.
**
** 对于轮子的右边,g(y)符合离散规律。
** 轮子转动后,g(y)会变化,具体体现为:g(y) = abOriginR[y + 26 - idxRotate],y = (abReverseR[g(y)] + idxRotate) % 26.
*/
const u8 abRvrsSlowR[WHEEL_LOOP_SIZE] = {
    
     3,21,1,13,15,23,11,9,17,5,14,18,25,6,2,10,16,20,4,8,0,12,19,24,22,7 };
const u8 abRvrsMidR [WHEEL_LOOP_SIZE] = {
    
     1,11,5,3,9,2,19,21,23,20,14,7,18,6,4,10,25,15,13,0,22,12,8,17,16,24 };
const u8 abRvrsFastR[WHEEL_LOOP_SIZE] = {
    
     24,22,7,10,12,19,23,0,14,6,9,15,8,25,20,17,3,1,18,4,21,5,11,13,16,2 };

typedef struct _WHEEL_CIPHER_T{
    
    
    u8 idxSlow;
    u8 idxMid;
    u8 idxFast;
    u8 f0Slow;
    u8 f0Mid;
    u8 f0Fast;
}_WHEEL_CIPHER_T;
_WHEEL_CIPHER_T stWheelCipher = {
    
    0,0,0,VALUE_F0_SLOW,VALUE_F0_MID,VALUE_F0_FAST};

void wc_reset(void)
{
    
    
    stWheelCipher.idxFast = 0;
    stWheelCipher.idxMid  = 0;
    stWheelCipher.idxSlow = 0;
    stWheelCipher.f0Fast  = VALUE_F0_FAST;
    stWheelCipher.f0Mid   = VALUE_F0_MID;
    stWheelCipher.f0Slow  = VALUE_F0_SLOW;
}

void wc_rotate(void)
{
    
    
    stWheelCipher.idxFast = (stWheelCipher.idxFast + 1) % WHEEL_LOOP_SIZE;
    stWheelCipher.f0Fast  = (stWheelCipher.f0Fast + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
    if(!stWheelCipher.idxFast){
    
    
        stWheelCipher.idxMid = (stWheelCipher.idxMid + 1) % WHEEL_LOOP_SIZE;
        stWheelCipher.f0Mid  = (stWheelCipher.f0Mid + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
        if(!stWheelCipher.idxMid){
    
    
            stWheelCipher.idxSlow = (stWheelCipher.idxSlow + 1) % WHEEL_LOOP_SIZE;
            stWheelCipher.f0Slow  = (stWheelCipher.f0Slow + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
        }
    }
}

void wc_reverse(void)
{
    
    
    stWheelCipher.idxFast = (stWheelCipher.idxFast + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
    stWheelCipher.f0Fast  = (stWheelCipher.f0Fast + 1) % WHEEL_LOOP_SIZE;
    if(stWheelCipher.idxFast == WHEEL_LOOP_SIZE - 1){
    
    
        stWheelCipher.idxMid = (stWheelCipher.idxMid + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
        stWheelCipher.f0Mid  = (stWheelCipher.f0Mid + 1) % WHEEL_LOOP_SIZE;
        if(stWheelCipher.idxMid == WHEEL_LOOP_SIZE - 1){
    
    
            stWheelCipher.idxSlow = (stWheelCipher.idxSlow + WHEEL_LOOP_SIZE - 1) % WHEEL_LOOP_SIZE;
            stWheelCipher.f0Slow  = (stWheelCipher.f0Slow + 1) % WHEEL_LOOP_SIZE;
        }
    }
}

s8 wc_cipherToPlain(s8 ch)
{
    
    
    s8 offsetCh;
    u8 x, f0, gy, y;

    offsetCh = (ch < 'a')?'A':'a';
    /* decode: input -> y -> g(y) -> f(x) -> x -> previous wheel / output */
    /* fast wheel */
    y  = ch - offsetCh;
    gy = abFastR[(y + WHEEL_LOOP_SIZE - stWheelCipher.idxFast) % WHEEL_LOOP_SIZE];
    //f(x) = g(y)
    x  = (gy + WHEEL_LOOP_SIZE - stWheelCipher.f0Fast) % WHEEL_LOOP_SIZE; 
    /* middle wheel */
    //y = x
    gy = abMidR[(x + WHEEL_LOOP_SIZE - stWheelCipher.idxMid) % WHEEL_LOOP_SIZE];
    //f(x) = g(y)
    x  = (gy + WHEEL_LOOP_SIZE - stWheelCipher.f0Mid) % WHEEL_LOOP_SIZE;
    /* slow wheel */
    //y = x
    gy = abSlowR[(x + WHEEL_LOOP_SIZE - stWheelCipher.idxSlow) % WHEEL_LOOP_SIZE];
    //f(x) = g(y)
    x  = (gy + WHEEL_LOOP_SIZE - stWheelCipher.f0Slow) % WHEEL_LOOP_SIZE;
    
    return (s8)(x + offsetCh);
}

s8 wc_plainToCipher(s8 ch)
{
    
    
    s8 offsetCh;
    u8 x, f0, fx, y;
    
    offsetCh = (ch < 'a')?'A':'a';
    /* encrypt: input -> x -> f(x) -> g(y) -> y -> next wheel / output */
    //slow wheel
    x  = ch - offsetCh;
    fx = (stWheelCipher.f0Slow + x) % WHEEL_LOOP_SIZE;
    y  = (abRvrsSlowR[fx] + stWheelCipher.idxSlow) % WHEEL_LOOP_SIZE; //g(y) = f(x)
    //middle wheel
    fx = (stWheelCipher.f0Mid + y) % WHEEL_LOOP_SIZE; //x = y
    y  = (abRvrsMidR[fx] + stWheelCipher.idxMid) % WHEEL_LOOP_SIZE; //g(y) = f(x)
    //fast wheel
    fx = (stWheelCipher.f0Fast + y) % WHEEL_LOOP_SIZE; //x = y
    y  = (abRvrsFastR[fx] + stWheelCipher.idxFast) % WHEEL_LOOP_SIZE; //g(y) = f(x)
    
    return (s8)(y + offsetCh);
}

void wc_setState(s8 *str, u32 strSize)
{
    
    
    u32 dwRotateCnt = 0;

    for(u32 i = 0; i < strSize; i++){
    
    
        dwRotateCnt += CHECK_ALPHABET(str[i]);
    }

    stWheelCipher.idxFast = dwRotateCnt % WHEEL_LOOP_SIZE;
    dwRotateCnt /= WHEEL_LOOP_SIZE;
    stWheelCipher.idxMid = dwRotateCnt % WHEEL_LOOP_SIZE;
    dwRotateCnt /= WHEEL_LOOP_SIZE;
    stWheelCipher.idxSlow = dwRotateCnt % WHEEL_LOOP_SIZE;

    stWheelCipher.f0Fast  = (VALUE_F0_FAST + WHEEL_LOOP_SIZE - stWheelCipher.idxFast) % WHEEL_LOOP_SIZE;
    stWheelCipher.f0Mid   = (VALUE_F0_MID  + WHEEL_LOOP_SIZE - stWheelCipher.idxMid ) % WHEEL_LOOP_SIZE;
    stWheelCipher.f0Slow  = (VALUE_F0_SLOW + WHEEL_LOOP_SIZE - stWheelCipher.idxSlow) % WHEEL_LOOP_SIZE;
}

void wc_decode(s8 *str)
{
    
    
    u32 dwStrSize = strlen(str);

    wc_reset();
    wc_setState(str, dwStrSize);
    for(u32 i = dwStrSize; i; ){
    
    
        i--;
        if(CHECK_ALPHABET(str[i])){
    
    
            wc_reverse();
            str[i] = wc_cipherToPlain(str[i]);
        }
    }
}

void wc_encrypt(s8 *str)
{
    
    
    u32 dwStrSize = strlen(str);
    wc_reset();
    for(u32 i = 0; i < dwStrSize; i++){
    
    
        if(CHECK_ALPHABET(str[i])){
    
    
            str[i] = wc_plainToCipher(str[i]);
            wc_rotate();
        }
    }
}

static inline void wc_init(void){
    
    
    wc_reset();
}

int main(void)
{
    
    
    s8 str[4096] = {
    
    0};

    wc_init();
    LOG("please input plain text:\r\n");
    memcpy(str, "This is a wheel cipher test.", sizeof("This is a wheel cipher test."));
    //getInput(str);
    LOG("%s\r\n", str);
    wc_encrypt(str);
    LOG("encrpyt result:\r\n");
    LOG("%s\r\n", str);
    wc_decode(str);
    LOG("decode result:\r\n");
    LOG("%s\r\n", str);

    return 0;
}

猜你喜欢

转载自blog.csdn.net/jimaofu0494/article/details/109026434