249 lines
8.4 KiB
OCaml
249 lines
8.4 KiB
OCaml
|
(*---------------------------------------------------------------------------
|
||
|
Copyright (c) 2017 Vincent Bernardoff. All rights reserved.
|
||
|
Distributed under the ISC license, see terms at the end of the file.
|
||
|
---------------------------------------------------------------------------*)
|
||
|
|
||
|
let packet_length = 64
|
||
|
let channel = 0x0101
|
||
|
let apdu = 0x05
|
||
|
let ping = 0x02
|
||
|
|
||
|
module Status = struct
|
||
|
type t =
|
||
|
| Invalid_pin of int
|
||
|
| Incorrect_length
|
||
|
| Incompatible_file_structure
|
||
|
| Security_status_unsatisfied
|
||
|
| Conditions_of_use_not_satisfied
|
||
|
| Incorrect_data
|
||
|
| File_not_found
|
||
|
| Incorrect_params
|
||
|
| Ins_not_supported
|
||
|
| Technical_problem of int
|
||
|
| Ok
|
||
|
|
||
|
let of_int = function
|
||
|
| 0x6700 -> Incorrect_length
|
||
|
| 0x6981 -> Incompatible_file_structure
|
||
|
| 0x6982 -> Security_status_unsatisfied
|
||
|
| 0x6985 -> Conditions_of_use_not_satisfied
|
||
|
| 0x6a80 -> Incorrect_data
|
||
|
| 0x9404 -> File_not_found
|
||
|
| 0x6b00 -> Incorrect_params
|
||
|
| 0x6d00 -> Ins_not_supported
|
||
|
| 0x9000 -> Ok
|
||
|
| v when v >= 0x63c0 && v <= 0x63cf -> Invalid_pin (v land 0x0f)
|
||
|
| v when v >= 0x6f00 && v <= 0x6fff -> Technical_problem (v land 0xff)
|
||
|
| v -> invalid_arg (Printf.sprintf "Status.of_int: got 0x%x" v)
|
||
|
|
||
|
let to_string = function
|
||
|
| Invalid_pin i -> Printf.sprintf "Invalid pin %d" i
|
||
|
| Incorrect_length -> "Incorrect length"
|
||
|
| Incompatible_file_structure -> "Incompatible file structure"
|
||
|
| Security_status_unsatisfied -> "Security status unsatisfied"
|
||
|
| Conditions_of_use_not_satisfied -> "Conditions of use not satisfied"
|
||
|
| Incorrect_data -> "Incorrect data"
|
||
|
| File_not_found -> "File not found"
|
||
|
| Incorrect_params -> "Incorrect params"
|
||
|
| Ins_not_supported -> "Instruction not supported"
|
||
|
| Technical_problem i -> Printf.sprintf "Technical problem %d" i
|
||
|
| Ok -> "Ok"
|
||
|
|
||
|
let show t = to_string t
|
||
|
|
||
|
let pp ppf t =
|
||
|
Format.pp_print_string ppf (to_string t)
|
||
|
end
|
||
|
|
||
|
module Header = struct
|
||
|
type t = {
|
||
|
cmd : [`Ping | `Apdu] ;
|
||
|
seq : int ;
|
||
|
}
|
||
|
|
||
|
let read cs =
|
||
|
let open Cstruct in
|
||
|
if BE.get_uint16 cs 0 <> channel then
|
||
|
invalid_arg "Transport.read_header: invalid channel id" ;
|
||
|
let cmd = match get_uint8 cs 2 with
|
||
|
| 0x05 -> `Apdu
|
||
|
| 0x02 -> `Ping
|
||
|
| _ -> invalid_arg "Transport.read_header: invalid command tag"
|
||
|
in
|
||
|
let seq = BE.get_uint16 cs 3 in
|
||
|
{ cmd ; seq }, Cstruct.shift cs 5
|
||
|
|
||
|
let check_exn ?cmd ?seq t =
|
||
|
begin match cmd with
|
||
|
| None -> ()
|
||
|
| Some expected ->
|
||
|
if expected <> t.cmd then failwith "Header.check: unexpected command"
|
||
|
end ;
|
||
|
begin match seq with
|
||
|
| None -> ()
|
||
|
| Some expected ->
|
||
|
if expected <> t.seq then failwith "Header.check: unexpected seq num"
|
||
|
end
|
||
|
end
|
||
|
|
||
|
let write_ping ?(buf=Cstruct.create packet_length) h =
|
||
|
let open Cstruct in
|
||
|
BE.set_uint16 buf 0 channel ;
|
||
|
set_uint8 buf 2 ping ;
|
||
|
BE.set_uint16 buf 3 0 ;
|
||
|
memset (sub buf 5 59) 0 ;
|
||
|
match Hidapi.write h (to_bigarray (sub buf 0 packet_length)) with
|
||
|
| Error msg -> failwith msg
|
||
|
| Ok nb_written when nb_written <> packet_length -> failwith "Transport.write_ping"
|
||
|
| _ -> ()
|
||
|
|
||
|
let write_apdu
|
||
|
?pp
|
||
|
?(buf=Cstruct.create packet_length)
|
||
|
h p =
|
||
|
let apdu_len = Apdu.length p in
|
||
|
let apdu_buf = Cstruct.create apdu_len in
|
||
|
let _nb_written = Apdu.write apdu_buf p in
|
||
|
begin match pp with
|
||
|
| None -> ()
|
||
|
| Some pp ->
|
||
|
Format.fprintf pp "-> %a@." Cstruct.hexdump_pp apdu_buf
|
||
|
end ;
|
||
|
let apdu_p = ref 0 in (* pos in the apdu buf *)
|
||
|
let i = ref 0 in (* packet id *)
|
||
|
let open Cstruct in
|
||
|
|
||
|
(* write first packet *)
|
||
|
BE.set_uint16 buf 0 channel ;
|
||
|
set_uint8 buf 2 apdu ;
|
||
|
BE.set_uint16 buf 3 !i ;
|
||
|
BE.set_uint16 buf 5 apdu_len ;
|
||
|
let nb_to_write = (min apdu_len (packet_length - 7)) in
|
||
|
blit apdu_buf 0 buf 7 nb_to_write ;
|
||
|
begin match Hidapi.write h (to_bigarray (sub buf 0 packet_length)) with
|
||
|
| Error msg -> failwith msg
|
||
|
| Ok nb_written when nb_written <> packet_length ->
|
||
|
failwith "Transport.write_apdu"
|
||
|
| _ -> ()
|
||
|
end ;
|
||
|
apdu_p := !apdu_p + nb_to_write ;
|
||
|
incr i ;
|
||
|
|
||
|
(* write following packets *)
|
||
|
while !apdu_p < apdu_len do
|
||
|
memset buf 0 ;
|
||
|
BE.set_uint16 buf 0 channel ;
|
||
|
set_uint8 buf 2 apdu ;
|
||
|
BE.set_uint16 buf 3 !i ;
|
||
|
let nb_to_write = (min (apdu_len - !apdu_p) (packet_length - 5)) in
|
||
|
blit apdu_buf !apdu_p buf 5 nb_to_write ;
|
||
|
begin match Hidapi.write h (to_bigarray (sub buf 0 packet_length)) with
|
||
|
| Error err -> failwith err
|
||
|
| Ok nb_written when nb_written <> packet_length ->
|
||
|
failwith "Transport.write_apdu"
|
||
|
| _ -> ()
|
||
|
end ;
|
||
|
apdu_p := !apdu_p + nb_to_write ;
|
||
|
incr i
|
||
|
done
|
||
|
|
||
|
let read ?(buf=Cstruct.create packet_length) h =
|
||
|
let expected_seq = ref 0 in
|
||
|
let full_payload = ref (Cstruct.create 0) in
|
||
|
let payload = ref (Cstruct.create 0) in
|
||
|
(* let pos = ref 0 in *)
|
||
|
let rec inner () =
|
||
|
begin match Hidapi.read ~timeout:600000 h
|
||
|
(Cstruct.to_bigarray buf) packet_length with
|
||
|
| Error err -> failwith err
|
||
|
| Ok nb_read when nb_read <> packet_length ->
|
||
|
failwith (Printf.sprintf "Transport.read: read %d bytes" nb_read)
|
||
|
| _ -> ()
|
||
|
end ;
|
||
|
let hdr, buf = Header.read buf in
|
||
|
Header.check_exn ~seq:!expected_seq hdr ;
|
||
|
if hdr.seq = 0 then begin (* first frame *)
|
||
|
let len = Cstruct.BE.get_uint16 buf 0 in
|
||
|
let cs = Cstruct.shift buf 2 in
|
||
|
payload := Cstruct.create len ;
|
||
|
full_payload := !payload ;
|
||
|
let nb_to_read = min len (packet_length - 7) in
|
||
|
Cstruct.blit cs 0 !payload 0 nb_to_read ;
|
||
|
payload := Cstruct.shift !payload nb_to_read ;
|
||
|
(* pos := !pos + nb_to_read ; *)
|
||
|
expected_seq := !expected_seq + 1 ;
|
||
|
end else begin (* next frames *)
|
||
|
(* let rem = Bytes.length !payload - !pos in *)
|
||
|
let nb_to_read = min (Cstruct.len !payload) (packet_length - 5) in
|
||
|
Cstruct.blit buf 0 !payload 0 nb_to_read ;
|
||
|
payload := Cstruct.shift !payload nb_to_read ;
|
||
|
(* pos := !pos + nb_to_read ; *)
|
||
|
expected_seq := !expected_seq + 1
|
||
|
end ;
|
||
|
if Cstruct.len !payload = 0 then
|
||
|
if hdr.cmd = `Ping then Status.Ok, Cstruct.create 0
|
||
|
else
|
||
|
(* let sw_pos = Bytes.length !payload - 2 in *)
|
||
|
let payload_len = Cstruct.len !full_payload in
|
||
|
Status.of_int Cstruct.(BE.get_uint16 !full_payload (payload_len - 2)),
|
||
|
Cstruct.sub !full_payload 0 (payload_len - 2)
|
||
|
else inner ()
|
||
|
in
|
||
|
inner ()
|
||
|
|
||
|
let ping ?buf h =
|
||
|
write_ping ?buf h ;
|
||
|
match read ?buf h with
|
||
|
| Status.Ok, _ -> ()
|
||
|
| s, _ -> failwith ((Status.to_string s))
|
||
|
|
||
|
let apdu ?pp ?(msg="") ?buf h apdu =
|
||
|
write_apdu ?pp ?buf h apdu ;
|
||
|
match read ?buf h with
|
||
|
| Status.Ok, payload ->
|
||
|
begin match pp with
|
||
|
| None -> ()
|
||
|
| Some pp ->
|
||
|
Format.fprintf pp "<- %a %a@." Status.pp Status.Ok Cstruct.hexdump_pp payload
|
||
|
end ;
|
||
|
payload
|
||
|
| s, payload ->
|
||
|
begin match pp with
|
||
|
| None -> ()
|
||
|
| Some pp ->
|
||
|
Format.fprintf pp "<- %a %a@." Status.pp s Cstruct.hexdump_pp payload
|
||
|
end ;
|
||
|
failwith ((Status.to_string s) ^ " " ^ msg)
|
||
|
|
||
|
let write_payload
|
||
|
?pp ?(msg="write_payload") ?buf ?(mark_last=false) ~cmd ?p1 ?p2 h cs =
|
||
|
let rec inner cs =
|
||
|
let cs_len = Cstruct.len cs in
|
||
|
let lc = min Apdu.max_data_length cs_len in
|
||
|
let last = lc = cs_len in
|
||
|
let p1 = match last, mark_last, p1 with
|
||
|
| true, true, None -> Some 0x80
|
||
|
| true, true, Some p1 -> Some (0x80 lor p1)
|
||
|
| _ -> p1 in
|
||
|
let response = apdu ?pp ~msg ?buf h
|
||
|
Apdu.(create ?p1 ?p2 ~lc ~data:(Cstruct.sub cs 0 lc) cmd) in
|
||
|
if last then response
|
||
|
else inner (Cstruct.shift cs lc) in
|
||
|
if Cstruct.len cs = 0 then cs else inner cs
|
||
|
|
||
|
(*---------------------------------------------------------------------------
|
||
|
Copyright (c) 2017 Vincent Bernardoff
|
||
|
|
||
|
Permission to use, copy, modify, and/or distribute this software for any
|
||
|
purpose with or without fee is hereby granted, provided that the above
|
||
|
copyright notice and this permission notice appear in all copies.
|
||
|
|
||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||
|
---------------------------------------------------------------------------*)
|