增加了tac
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
+            }
+        }
+    }
 }