/*
 * (C) 2010 by Andreas Eversberg <jolly@eversberg.eu>
 *
 * All Rights Reserved
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#pragma once

#include <osmocom/gsm/protocol/gsm_04_08.h>
#include <osmocom/core/endian.h>

struct osmocom_ms;

/* 9.2 commands */
#define GSM1111_CLASS_GSM		0xa0
#define	GSM1111_INST_SELECT		0xa4
#define	GSM1111_INST_STATUS		0xf2
#define	GSM1111_INST_READ_BINARY	0xb0
#define	GSM1111_INST_UPDATE_BINARY	0xd6
#define	GSM1111_INST_READ_RECORD	0xb2
#define	GSM1111_INST_UPDATE_RECORD	0xdc
#define	GSM1111_INST_SEEK		0xa2
#define	GSM1111_INST_INCREASE		0x32
#define	GSM1111_INST_VERIFY_CHV		0x20
#define	GSM1111_INST_CHANGE_CHV		0x24
#define	GSM1111_INST_DISABLE_CHV	0x26
#define	GSM1111_INST_ENABLE_CHV		0x28
#define	GSM1111_INST_UNBLOCK_CHV	0x2c
#define	GSM1111_INST_INVALIDATE		0x04
#define	GSM1111_INST_REHABLILITATE	0x44
#define	GSM1111_INST_RUN_GSM_ALGO	0x88
#define	GSM1111_INST_SLEEP		0xfa
#define	GSM1111_INST_GET_RESPONSE	0xc0
#define	GSM1111_INST_TERMINAL_PROFILE	0x10
#define	GSM1111_INST_ENVELOPE		0xc2
#define	GSM1111_INST_FETCH		0x12
#define	GSM1111_INST_TERMINAL_RESPONSE	0x14

/* 9.3 access conditions */
#define GSM1111_ACC_ALWAYS		0x0
#define GSM1111_ACC_CHV1		0x1
#define GSM1111_ACC_CHV2		0x2
#define GSM1111_ACC_RFU			0x3
#define GSM1111_ACC_NEW			0xf
/* others are ADM */

/* 9.3 type of file */
#define GSM1111_TOF_RFU			0x00
#define GSM1111_TOF_MF			0x01
#define GSM1111_TOF_DF			0x02
#define GSM1111_TOF_EF			0x04

/* 9.3 struct of file */
#define GSM1111_SOF_TRANSPARENT		0x00
#define GSM1111_SOF_LINEAR		0x01
#define GSM1111_SOF_CYCLIC		0x03

/* 9.4 status */
#define GSM1111_STAT_NORMAL		0x90
#define GSM1111_STAT_PROACTIVE		0x91
#define GSM1111_STAT_DL_ERROR		0x9e
#define GSM1111_STAT_RESPONSE		0x9f
#define GSM1111_STAT_RESPONSE_TOO	0x61
#define GSM1111_STAT_APP_TK_BUSY	0x93
#define GSM1111_STAT_MEM_PROBLEM	0x92
#define GSM1111_STAT_REFERENCING	0x94
#define GSM1111_STAT_SECURITY		0x98
#define GSM1111_STAT_INCORR_P3		0x67
#define GSM1111_STAT_INCORR_P1_P2	0x6b
#define GSM1111_STAT_UKN_INST		0x6d
#define GSM1111_STAT_WRONG_CLASS	0x6e
#define GSM1111_STAT_TECH_PROBLEM	0x6f

/* 9.4.4 Referencing management SW2 */
#define GSM1111_REF_NO_EF		0x00
#define GSM1111_REF_OUT_OF_RANGE	0x02
#define GSM1111_REF_FILE_NOT_FOUND	0x04
#define GSM1111_REF_FILE_INCONSI	0x08

/* 9.4.5 Security management SW2 */
#define GSM1111_SEC_NO_CHV		0x02
#define GSM1111_SEC_NO_ACCESS		0x04
#define GSM1111_SEC_CONTRA_CHV		0x08
#define GSM1111_SEC_CONTRA_INVAL	0x10
#define GSM1111_SEC_BLOCKED		0x40
#define GSM1111_SEC_MAX_VALUE		0x50

/* messages from application to sim client */
enum {
	/* requests */
	SIM_JOB_READ_BINARY,
	SIM_JOB_UPDATE_BINARY,
	SIM_JOB_READ_RECORD,
	SIM_JOB_UPDATE_RECORD,
	SIM_JOB_SEEK_RECORD,
	SIM_JOB_INCREASE,
	SIM_JOB_INVALIDATE,
	SIM_JOB_REHABILITATE,
	SIM_JOB_RUN_GSM_ALGO,
	SIM_JOB_PIN1_UNLOCK,
	SIM_JOB_PIN1_CHANGE,
	SIM_JOB_PIN1_DISABLE,
	SIM_JOB_PIN1_ENABLE,
	SIM_JOB_PIN1_UNBLOCK,
	SIM_JOB_PIN2_UNLOCK,
	SIM_JOB_PIN2_CHANGE,
	SIM_JOB_PIN2_UNBLOCK,

	/* results */
	SIM_JOB_OK,
	SIM_JOB_ERROR,
};

/* messages from sim client to application */
#define SIM_JOB_OK		0
#define SIM_JOB_ERROR		1

/* error causes */
#define SIM_CAUSE_NO_SIM	0	/* no SIM present, if detectable */
#define SIM_CAUSE_SIM_ERROR	1	/* any error while reading SIM */
#define SIM_CAUSE_REQUEST_ERROR	2	/* error in request */
#define SIM_CAUSE_PIN1_REQUIRED	3	/* CHV1 is required for access */
#define SIM_CAUSE_PIN2_REQUIRED	4	/* CHV2 is required for access */
#define SIM_CAUSE_PIN1_BLOCKED	5	/* CHV1 was entered too many times */
#define SIM_CAUSE_PIN2_BLOCKED	6	/* CHV2 was entered too many times */
#define SIM_CAUSE_PUC_BLOCKED	7	/* unblock entered too many times */

/* job states */
enum {
	SIM_JST_IDLE = 0,
	SIM_JST_SELECT_MFDF,		/* SELECT sent */
	SIM_JST_SELECT_MFDF_RESP,	/* GET RESPONSE sent */
	SIM_JST_SELECT_EF,		/* SELECT sent */
	SIM_JST_SELECT_EF_RESP,		/* GET RESPONSE sent */
	SIM_JST_WAIT_FILE,		/* file command sent */
	SIM_JST_RUN_GSM_ALGO,		/* wait for algorithm to process */
	SIM_JST_RUN_GSM_ALGO_RESP,	/* wait for response */
	SIM_JST_PIN1_UNLOCK,
	SIM_JST_PIN1_CHANGE,
	SIM_JST_PIN1_DISABLE,
	SIM_JST_PIN1_ENABLE,
	SIM_JST_PIN1_UNBLOCK,
	SIM_JST_PIN2_UNLOCK,
	SIM_JST_PIN2_CHANGE,
	SIM_JST_PIN2_UNBLOCK,
};

#define MAX_SIM_PATH_LENGTH	6 + 1 /* one for the termination */

struct gsm_sim_handler {
	struct llist_head	entry;

	uint32_t		handle;
	void			(*cb)(struct osmocom_ms *ms, struct msgb *msg);
};

struct gsm_sim {
	struct llist_head	handlers; /* gsm_sim_handler */
	struct llist_head	jobs; /* messages */
	uint16_t path[MAX_SIM_PATH_LENGTH];
	uint16_t file;

	struct msgb		*job_msg;
	uint32_t		job_handle;
	int			job_state;

	uint8_t			reset;
	uint8_t			chv1_remain, chv2_remain;
	uint8_t			unblk1_remain, unblk2_remain;

	/* APDU cache (used by GSMTAP) */
	uint8_t apdu_data[256 + 7];
	uint16_t apdu_len;
};

struct sim_hdr {
	int handle;
	uint8_t job_type;
	uint16_t path[MAX_SIM_PATH_LENGTH];
	uint16_t file;
	uint8_t rec_no, rec_mode; /* in case of record */
	uint8_t seek_type_mode; /* in case of seek command */
};

#define SIM_ALLOC_SIZE		512
#define SIM_ALLOC_HEADROOM	64

struct msgb *gsm_sim_msgb_alloc(uint32_t handle, uint8_t job_type);
uint32_t sim_open(struct osmocom_ms *ms,
	void (*cb)(struct osmocom_ms *ms, struct msgb *msg));
void sim_close(struct osmocom_ms *ms, uint32_t handle);
void sim_job(struct osmocom_ms *ms, struct msgb *msg);

/* Section 9.2.1 (response to selecting DF or MF) */
struct gsm1111_response_mfdf {
	uint16_t rfu1;
	uint16_t free_mem;
	uint16_t file_id;
	uint8_t tof;
	uint8_t	rfu2[5];
	uint8_t length;
	uint8_t gsm_data[0];
} __attribute__ ((packed));

struct gsm1111_response_mfdf_gsm {
#if OSMO_IS_LITTLE_ENDIAN
	uint8_t file_char;
	uint8_t num_df;
	uint8_t num_ef;
	uint8_t num_codes;
	uint8_t rfu1;
	uint8_t chv1_remain:4,
		 rfu2:3,
		 chv1_init:1;
	uint8_t unblk1_remain:4,
		 rfu3:3,
		 unblk1_init:1;
	uint8_t chv2_remain:4,
		 rfu4:3,
		 chv2_init:1;
	uint8_t unblk2_remain:4,
		 rfu5:3,
		 unblk2_init:1;
	uint8_t more_data[0];
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
	uint8_t file_char;
	uint8_t num_df;
	uint8_t num_ef;
	uint8_t num_codes;
	uint8_t rfu1;
	uint8_t chv1_init:1, rfu2:3, chv1_remain:4;
	uint8_t unblk1_init:1, rfu3:3, unblk1_remain:4;
	uint8_t chv2_init:1, rfu4:3, chv2_remain:4;
	uint8_t unblk2_init:1, rfu5:3, unblk2_remain:4;
	uint8_t more_data[0];
#endif
} __attribute__ ((packed));

/* Section 9.2.1 (response to selecting EF) */
struct gsm1111_response_ef {
#if OSMO_IS_LITTLE_ENDIAN
	uint16_t rfu1;
	uint16_t file_size;
	uint16_t file_id;
	uint8_t tof;
	uint8_t inc_allowed;
	uint8_t	acc_update:4,
		 acc_read:4;
	uint8_t	rfu2:4,
		 acc_inc:4;
	uint8_t	acc_inval:4,
		 acc_reha:4;
	uint8_t not_inval:1,
		 rfu3:1,
		 ru_inval:1,
		 rfu4:5;
	uint8_t length;
	uint8_t structure;
#elif OSMO_IS_BIG_ENDIAN
/* auto-generated from the little endian part above (libosmocore/contrib/struct_endianness.py) */
	uint16_t rfu1;
	uint16_t file_size;
	uint16_t file_id;
	uint8_t tof;
	uint8_t inc_allowed;
	uint8_t	acc_read:4, acc_update:4;
	uint8_t	acc_inc:4, rfu2:4;
	uint8_t	acc_reha:4, acc_inval:4;
	uint8_t rfu4:5, ru_inval:1, rfu3:1, not_inval:1;
	uint8_t length;
	uint8_t structure;
#endif
} __attribute__ ((packed));

/* Section 10.3.17 */
struct gsm1111_ef_loci {
	uint32_t tmsi;
	struct gsm48_loc_area_id lai;
	uint8_t tmsi_time;
	uint8_t lupd_status; /* enum gsm1111_ef_loci_lupd_status */
} __attribute__ ((packed));

enum gsm1111_ef_loci_lupd_status {
	GSM1111_EF_LOCI_LUPD_ST_UPDATED = 0,
	GSM1111_EF_LOCI_LUPD_ST_NOT_UPDATED = 1,
	GSM1111_EF_LOCI_LUPD_ST_PLMN_NOT_ALLOWED = 2,
	GSM1111_EF_LOCI_LUPD_ST_LA_NOT_ALLOWED = 3,
	GSM1111_EF_LOCI_LUPD_ST_RESERVED = 7,
};

/* Section 10.3.33 */
struct gsm1111_ef_locigprs {
	uint32_t ptmsi;
	uint16_t ptmsi_sig_hi;
	uint8_t ptmsi_sig_lo;
	struct gsm48_ra_id rai;
	uint8_t rau_status; /* enum gsm1111_ef_locigprs_rau_status */
} __attribute__ ((packed));

enum gsm1111_ef_locigprs_rau_status {
	GSM1111_EF_LOCIGPRS_RAU_ST_UPDATED = 0,
	GSM1111_EF_LOCIGPRS_RAU_ST_NOT_UPDATED = 1,
	GSM1111_EF_LOCIGPRS_RAU_ST_PLMN_NOT_ALLOWED = 2,
	GSM1111_EF_LOCIGPRS_RAU_ST_RA_NOT_ALLOWED = 3,
	GSM1111_EF_LOCIGPRS_RAU_ST_RESERVED = 7,
};

/* Section 10.5.1 */
struct gsm1111_ef_adn {
	uint8_t len_bcd;
	uint8_t ton_npi;
	uint8_t number[10];
	uint8_t capa_conf;
	uint8_t ext_id;
} __attribute__ ((packed));

/* Section 10.5.6 */
struct gsm1111_ef_smsp {
	uint8_t par_ind;
	uint8_t tp_da[12];
	uint8_t ts_sca[12];
	uint8_t tp_proto;
	uint8_t tp_dcs;
	uint8_t tp_vp;
} __attribute__ ((packed));

int sim_apdu_resp(struct osmocom_ms *ms, struct msgb *msg);
int gsm_sim_init(struct osmocom_ms *ms);
int gsm_sim_exit(struct osmocom_ms *ms);
int gsm_sim_job_dequeue(struct osmocom_ms *ms);


