增加了tac
diff --git a/Makefile b/Makefile
index 7a824c5..d030da8 100644
--- a/Makefile
+++ b/Makefile
@@ -11,5 +11,7 @@
 	$(CARGO) build --target aarch64-linux-android --release
 	$(CARGO) build --target armv7-linux-androideabi --release
 
+
+
 clean:
 	$(RM) -rf target
diff --git a/dlqrcode/Cargo.toml b/dlqrcode/Cargo.toml
index e51e7eb..90ea0f7 100644
--- a/dlqrcode/Cargo.toml
+++ b/dlqrcode/Cargo.toml
@@ -13,8 +13,5 @@
 sha2 = "0.9.1"
 totp-rs = "0.6.2"
 hex = "0.4.2"
-base64-url = "1.4.7"
-
-[dependencies.magic-crypt]
-version = "*"
-default-features = false
+log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }
+simple_logger = "1.11.0"
diff --git a/dlqrcode/src/lib.rs b/dlqrcode/src/lib.rs
index a28a70d..81cbe91 100644
--- a/dlqrcode/src/lib.rs
+++ b/dlqrcode/src/lib.rs
@@ -1,11 +1,14 @@
-extern crate base64_url;
+extern crate base64;
 extern crate hex;
 
+use simple_logger::SimpleLogger;
 use std::fmt;
 use std::slice;
 use std::time::Instant;
 use std::time::SystemTime;
 
+use log::debug;
+
 use sha2::{Digest, Sha256};
 
 use aes::Aes256;
@@ -19,6 +22,15 @@
 const TOTP_SKEW: u8 = 3;
 static QR_FIELD_DILIMITER: &str = ":";
 
+static MASTER: &str = "wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA=";
+
+static MASTER_IV: [u8; 16] = [0u8; 16];
+
+fn setup_logger() -> bool {
+    SimpleLogger::new().init().unwrap();
+    true
+}
+
 pub struct DaliQrCode {
     master_key: Vec<u8>,
     iv: Vec<u8>,
@@ -69,7 +81,7 @@
         self.sign = sign.to_vec();
     }
 
-    fn to_qrdata(&self) -> String {
+    pub fn to_qrdata(&self) -> String {
         let v = vec![
             String::from(&self.uid),
             String::from(&self.cardno),
@@ -111,6 +123,8 @@
         skew: Option<u8>,
         seed: Option<Vec<u8>>,
     ) -> Result<Self> {
+        setup_logger();
+
         let key = key.to_vec();
         if key.len() != KEY_LEN {
             return Err(DecodeError::new(&format!(
@@ -167,7 +181,7 @@
         let mut qr_fields: Vec<Vec<u8>> = Vec::new();
 
         self.split_qrdata(&qrcode, &mut qr_fields)?;
-        
+
         let qr_data = DaliQrData::from_qrcode(&qr_fields)?;
         if qr_data.uid.len() < 16 {
             return Err(DecodeError::new("uid must grater than 16"));
@@ -186,18 +200,16 @@
 
     fn check_qrcode_sign(&self, qr_data: &DaliQrData) -> Result<()> {
         let sign = self.calc_sign(qr_data);
-        if qr_data.sign == sign {
+        debug!("sign expect : {}", hex::encode(sign.clone()));
+        debug!("sign actual : {}", hex::encode(qr_data.sign.clone()));
+        if sign.iter().cloned().eq(qr_data.sign.iter().cloned()) {
             Ok(())
         } else {
             Err(DecodeError::new("sign is invalid"))
         }
     }
 
-    fn decode_qrcode(
-        &self,
-        qrcode: *const u8,
-        len: usize,
-    ) -> Result<Vec<u8>> {
+    fn decode_qrcode(&self, qrcode: *const u8, len: usize) -> Result<Vec<u8>> {
         let cipher = match Aes256Cbc::new_var(&self.master_key, &self.iv) {
             Ok(c) => c,
             Err(e) => return Err(DecodeError::new(&format!("aes key error {:?}", e))),
@@ -205,7 +217,7 @@
 
         let qrcode = unsafe {
             let s = slice::from_raw_parts(qrcode, len);
-            if let Ok(code) = base64_url::decode(s) {
+            if let Ok(code) = base64::decode_config(s, base64::URL_SAFE) {
                 code
             } else {
                 return Err(DecodeError::new("data base64 decode error"));
@@ -223,10 +235,11 @@
             .duration_since(SystemTime::UNIX_EPOCH)
             .unwrap()
             .as_secs();
+        debug!("totp system time : {}, offset {}", time, secs_offset);
         if secs_offset > 0 {
             time + (secs_offset as u64)
         } else {
-            time - (secs_offset as u64)
+            time - (-secs_offset as u64)
         }
     }
 
@@ -246,7 +259,10 @@
         buffer.push(QR_FIELD_DILIMITER.as_bytes()[0]);
         buffer.extend_from_slice(qr_data.sign.as_slice());
         let crypt_data = cipher.encrypt_vec(buffer.as_slice());
-        Ok(base64_url::encode(crypt_data.as_slice()))
+        Ok(base64::encode_config(
+            crypt_data.as_slice(),
+            base64::URL_SAFE,
+        ))
     }
 
     fn calc_sign(&self, qr_data: &DaliQrData) -> Vec<u8> {
@@ -255,6 +271,7 @@
         hasher.update(qr_data.uid.as_bytes());
         let salt = hasher.finalize();
         let mut hasher = Sha256::new();
+        debug!("qrdata sign : {}", qr_data.to_qrdata());
         hasher.update(qr_data.to_qrdata().as_bytes());
         hasher.update(salt);
         hasher.finalize().to_vec()
@@ -271,16 +288,60 @@
         }
         let sign = self.calc_sign(qr_data);
         qr_data.update_sign(&sign);
+        debug!("encode qrcode sign : {}", hex::encode(sign));
         self.encode_qrcode(qr_data)
     }
 }
 
+
+pub fn transaction_sign(qrdata: &DaliQrData) -> String {
+    let sign_str = qrdata.to_qrdata();
+    let key = Aes256Cbc::new_var(MASTER.as_bytes(), &MASTER_IV).unwrap();
+
+    let cipher = key.encrypt_vec(sign_str.as_bytes());
+    base64::encode_config(cipher, base64::URL_SAFE)
+}
+
+pub fn transaction_tac(
+    cardno: &str,
+    amount: i32,
+    term_date_time: &str,
+    sign: &str,
+) -> Result<String> {
+    let sign = unsafe {
+        if let Ok(s) = base64::decode_config(sign, base64::URL_SAFE) {
+            let key = Aes256Cbc::new_var(MASTER.as_bytes(), &MASTER_IV).unwrap();
+            if let Ok(k) = key.decrypt_vec(&s[..]) {
+                String::from_utf8_unchecked(k)
+            } else {
+                return Err(DecodeError::new("sign data error"));
+            }
+        } else {
+            return Err(DecodeError::new("sign format invalid"));
+        }
+    };
+
+    let fields: Vec<&str> = sign.split(QR_FIELD_DILIMITER).collect();
+    if fields.len() < 4 || fields[1] != cardno {
+        return Err(DecodeError::new("sign is invalidated"));
+    }
+
+    let tac_buffer = format!("{}{}{}", amount, term_date_time, "{dlsmk}");
+    let mut hasher = Sha256::new();
+    hasher.update(sign);
+    hasher.update(tac_buffer.as_bytes());
+    let tac = base64::encode_config(hasher.finalize(), base64::URL_SAFE);
+    Ok(tac)
+}
+
+
 #[cfg(test)]
 mod tests {
     use super::*;
     use base64::decode;
     use std::convert::TryInto;
     const KEYLEN: usize = 32;
+
     #[test]
     fn it_works() {
         assert_eq!(2 + 2, 4);
@@ -321,7 +382,7 @@
 
     fn get_key() -> ([u8; KEYLEN], [u8; 16]) {
         let mut key = [0u8; KEYLEN];
-        let s = decode("Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU=").unwrap();
+        let s = base64::decode("wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA=").unwrap();
         key.clone_from_slice(&s.as_slice()[..KEYLEN]);
 
         let iv: [u8; 16] = {
@@ -363,25 +424,58 @@
         }
     }
 
-    #[test]
-    fn check_qrcode_decode() {
-        let (key, iv) = get_key();
-        let mut qr_data = DaliQrData::new();
+    fn get_qrdata(qr_data: &mut DaliQrData) -> () {
         qr_data.uid = String::from("0a5de6ce985d43989b7ebe64ad8eb9c3");
         qr_data.cardno = String::from("00001252");
         qr_data.cardtype = String::from("80");
+    }
 
-        let dali_qrcode = DaliQrCode::new(key, Some(iv), Some(60u64), Some(5u8), None).unwrap();
+    fn test_qr_decoder(decoder: &DaliQrCode, qrcode: &str, offset: i32) -> bool {
+        match decoder.decode(qrcode.as_ptr(), qrcode.len(), offset) {
+            Ok(_) => true,
+            Err(_) => false,
+        }
+    }
+
+    #[test]
+    fn check_qrcode_decode() {
+        let (key, iv) = get_key();
+
+        let mut qr_data = DaliQrData::new();
+        get_qrdata(&mut qr_data);
+
+        let dali_qrcode = DaliQrCode::new(key, Some(iv), Some(30u64), Some(3u8), None).unwrap();
 
         match dali_qrcode.encode(&mut qr_data, 0) {
             Ok(qrcode) => {
-                if let Err(e) = dali_qrcode.decode(qrcode.as_ptr(), qrcode.len(), 0) {
-                    panic!("error {}", e);
-                }
+                // println!("qrdata : {:?}", qr_data);
+                debug!("encode qrcode : {}", qrcode);
+                assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, -20), true);
+                assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, -300), false);
+                assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, 49), true);
+                assert_eq!(test_qr_decoder(&dali_qrcode, &qrcode, 65), true);
             }
             Err(e) => {
                 panic!("error {}", e);
             }
         }
     }
+
+    #[test]
+    fn test_tac() {
+        let qrcode = "Ntd0wHly2IiweNP61JiewTmZ27JM3Vs-vjZaz45Ly8G1_lgcdkMw1QClLfKm-9pVTvT0pvrfhMpRgvh9UcQ26UYibVeczWYMtatN4x1OlsGM2cKXooCT1d-ika480wWq";
+        let tac = "8tck8-ljatwwcbtzlgty3l-tq7td3evbjvnzkuusrew";
+
+        let (key, iv) = get_key();
+        let decoder = DaliQrCode::new(key, Some(iv), Some(30u64), Some(3u8), None).unwrap();
+        match decoder.decode(qrcode.as_bytes().as_ptr(), qrcode.len(), -300) {
+            Ok(data) => {
+                let sign = transaction_sign(&data);
+                let actual_tac =
+                    transaction_tac(&data.cardno, 100, "20201102135834", &sign).unwrap();
+                assert_eq!(actual_tac, tac);
+            }
+            Err(e) => panic!("qrcode error {:?}", e),
+        }
+    }
 }
diff --git a/dlsmk/Cargo.toml b/dlsmk/Cargo.toml
index 48e7107..eb237b5 100644
--- a/dlsmk/Cargo.toml
+++ b/dlsmk/Cargo.toml
@@ -17,3 +17,5 @@
 dlqrcode = { path = "../dlqrcode" }
 libc = "0.2.80"
 hex = "0.4.2"
+base64 = "0.13.0"
+log = { version = "0.4", features = ["max_level_debug", "release_max_level_warn"] }
diff --git a/dlsmk/src/lib.rs b/dlsmk/src/lib.rs
index fa982de..709b240 100644
--- a/dlsmk/src/lib.rs
+++ b/dlsmk/src/lib.rs
@@ -1,9 +1,24 @@
+extern crate base64;
+extern crate hex;
+
 #[cfg(test)]
 mod tests {
     #[test]
     fn it_works() {
         assert_eq!(2 + 2, 4);
     }
+
+    #[test]
+    fn test_key() {
+        let key = "wDp3/3NPEi+R0peokVv010GkDk1mRTp3tUB/lCEVRAA=";
+        let key_buffer = base64::decode_config(key, base64::STANDARD).unwrap();
+        let s = hex::encode(key_buffer.as_slice());
+        let key_string = hex::decode(format!("{}{:04x}{:02x}", s, 30, 5)).unwrap();
+        println!(
+            "key : {}",
+            base64::encode_config(key_string, base64::URL_SAFE)
+        );
+    }
 }
 
 // use std::ffi::{CStr, CString};
@@ -18,20 +33,21 @@
 //     CString::new("Hello ".to_owned()).unwrap().into_raw()
 // }
 
-// #[cfg(target_os = "android")]
 #[cfg(any(target_family = "unix", target_os = "android"))]
-// #[cfg(target_family = "unix")]
 #[allow(non_snake_case)]
 pub mod android {
+    extern crate base64;
     extern crate jni;
 
     use self::jni::objects::{JClass, JMap, JObject, JString};
-    use self::jni::sys::{jboolean, jlong, JNI_FALSE, JNI_TRUE};
+    use self::jni::sys::{jboolean, jint, jlong, JNI_FALSE, JNI_TRUE};
     use self::jni::JNIEnv;
     // use super::*;
     use libc;
     use std::slice;
 
+    use log::debug;
+
     use dlqrcode::DaliQrCode;
 
     fn put_data(env: &JNIEnv, map: &JMap, key: &str, value: &str) {
@@ -75,11 +91,17 @@
 
         let qrdata = env.get_map(result).expect("invalid qrdata map");
 
-        let key = {
+        let (key, step, skew) = {
             let mut k = [0u8; 32];
-            if let Ok(v) = hex::decode(key.as_slice()) {
-                k.clone_from_slice(v.as_slice());
-                k
+            if let Ok(v) = base64::decode_config(key.as_slice(), base64::URL_SAFE) {
+                if v.len() != 35 {
+                    put_data(&env, &qrdata, "error", "key format error");
+                    return JNI_FALSE;
+                }
+                k.clone_from_slice(&v.as_slice()[..32]);
+                let step = ((v[32] as u64) << 8) | (v[33] as u64);
+                let skew = v[34] as u8;
+                (k, step, skew)
             } else {
                 put_data(
                     &env,
@@ -91,7 +113,8 @@
             }
         };
 
-        let decode = match DaliQrCode::new(key, None, None, None, None) {
+        debug!("TOTP step <{}> , skew <{}>", step, skew);
+        let decoder = match DaliQrCode::new(key, None, Some(step), Some(skew), None) {
             Ok(d) => d,
             Err(e) => {
                 let s = format!("invalid input parameter {:?}", e);
@@ -100,26 +123,13 @@
             }
         };
 
-        match decode.decode(qrcode.as_ptr(), qrcode.len(), offset as i32) {
+        match decoder.decode(qrcode.as_ptr(), qrcode.len(), offset as i32) {
             Ok(d) => {
-                qrdata
-                    .put(
-                        *env.new_string("cardno").unwrap(),
-                        *env.new_string(d.cardno).unwrap(),
-                    )
-                    .unwrap();
-                qrdata
-                    .put(
-                        *env.new_string("cardtype").unwrap(),
-                        *env.new_string(d.cardtype).unwrap(),
-                    )
-                    .unwrap();
-                qrdata
-                    .put(
-                        *env.new_string("uid").unwrap(),
-                        *env.new_string(d.uid).unwrap(),
-                    )
-                    .unwrap();
+                put_data(&env, &qrdata, "cardno", &d.cardno);
+                put_data(&env, &qrdata, "cardtype", &d.cardtype);
+                put_data(&env, &qrdata, "uid", &d.uid);
+                let sign = dlqrcode::transaction_sign(&d);
+                put_data(&env, &qrdata, "sign", &sign);
                 return JNI_TRUE;
             }
             Err(e) => {
@@ -129,4 +139,56 @@
             }
         };
     }
+
+    #[no_mangle]
+    pub unsafe extern "C" fn Java_com_supwisdom_dlsmk_DLSMKQrCode_genTac(
+        env: JNIEnv,
+        _: JClass,
+        qrsign: JString,
+        cardno: JString,
+        amount: jint,
+        termDatetime: JString,
+        result: JObject,
+    ) -> jboolean {
+        let qrsign = {
+            let s = env.get_string(qrsign).expect("invalid key string").as_ptr();
+            let keylen = libc::strlen(s);
+            String::from_raw_parts(s as *mut u8, keylen, keylen)
+        };
+        let cardno = {
+            let s = env
+                .get_string(cardno)
+                .expect("invalid cardno string")
+                .as_ptr();
+            let len = libc::strlen(s);
+            String::from_raw_parts(s as *mut u8, len, len)
+        };
+
+        let termDatetime = {
+            let s = env
+                .get_string(termDatetime)
+                .expect("invalid datetime")
+                .as_ptr();
+            let len = libc::strlen(s);
+            String::from_raw_parts(s as *mut u8, len, len)
+        };
+
+        let result = env.get_map(result).expect("invalid qrdata map");
+
+        if termDatetime.len() != 14 {
+            put_data(&env, &result, "error", "term datetime must be 14 chars");
+            return JNI_FALSE;
+        }
+
+        match dlqrcode::transaction_tac(&cardno, amount, &termDatetime, &qrsign) {
+            Ok(tac) => {
+                put_data(&env, &result, "tac", &tac);
+                JNI_TRUE
+            }
+            Err(e) => {
+                put_data(&env, &result, "error", &format!("{:?}", e));
+                JNI_FALSE
+            }
+        }
+    }
 }
diff --git a/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java b/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
index 219284f..9921c52 100644
--- a/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
+++ b/java/src/main/java/com/supwisdom/dlsmk/DLSMK.java
@@ -25,15 +25,23 @@
 
     public static void main(String[] args) {
         System.setProperty("java.library.path" , ".");
-        String key = encodeHex(Base64.decodeBase64("Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU="));
+        String key = "wDp3_3NPEi-R0peokVv010GkDk1mRTp3tUB_lCEVRAAAHgU=";
 
-        String qrcode = "6lHyFX_vg5U2hymn8OsdNUD7dT0-sCmEQkKrm9cnzHlku6-FYxuL6nP5YR2Fve8Sfj-Asd-3dfQUkaiqqbfQWO8B_811B3uhHmGm9IjlpLicz_c1H1_ORb9tJl-IhMKu";
+        String qrcode = "Ntd0wHly2IiweNP61JiewTmZ27JM3Vs-vjZaz45Ly8FoE5mB8QfvJBNJOGYsDQsFBrJqutQxVnyGfWcCbocIbWe2MwOdnfjZLLjAMZKQe_cDKntkfU2rJIo93IyS4TdJ";
 
         Map<Object, Object> result = new HashMap();
         if (DLSMKQrCode.decode(key, qrcode, 0L, result)) {
             System.out.println("Decode OK");
-            System.out.format("cardno : %s", result.get("cardno").toString());
-            System.out.format("cardtype : %s", result.get("cardtype").toString());
+            String cardno = result.get("cardno").toString();
+            System.out.format("cardno : %s\n", cardno);
+            System.out.format("cardtype : %s\n", result.get("cardtype").toString());
+            String sign = result.get("sign").toString();
+            System.out.format("sign : %s", sign);
+            System.out.println();
+
+            result.clear();
+            DLSMKQrCode.genTac(sign, cardno, 100, "20201102164539", result);
+            System.out.format("tac : %s", result.get("tac"));
             System.out.println();
         } else {
             System.out.format("decode failed: %s", result.get("error").toString());
diff --git a/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java b/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java
index 257ba7b..c048c46 100644
--- a/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java
+++ b/java/src/main/java/com/supwisdom/dlsmk/DLSMKQrCode.java
@@ -7,12 +7,24 @@
         System.loadLibrary("dlsmk");
     }
     /**
-     * @param keyHex - 主密钥, HEX 格式
+     * @param keyHex - 主密钥,base64编码
      * @param qrcodeBase64 - 二维码数据
      * @param secsOffset - 市民卡平台与App的时间差,单位秒,可以为负数
-     * @param result - 结果 , key in [cardno, cardtype, error]
+     * @param result - 结果 , key in [cardno, cardtype, sign, error]
      * @return - true 成功,false 失败
      */
-    public static native boolean decode(String keyHex,String qrcodeBase64, 
-                                        Long secsOffset, Map<Object, Object> result);
+    public static native boolean decode(String key,String qrcodeBase64, 
+                                        long secsOffset, Map<Object, Object> result);
+
+
+    /**
+     * @param sign - 签名解码二维码时获取到的签名
+     * @param cardno - 市民卡号
+     * @param amount -交金额, 单位 分
+     * @param termDatetime - 终端时间 例如: 20201023153948
+     * @param result j结果  , key in [tac, error]
+     * @return - 
+     */
+    public static native boolean genTac(String sign, String cardno, int amount,
+                                     String termDatetime, Map<Object, Object> result);
 }