<?php
/*
* 本 PHP スクリプトは PD Repeater の PD Web を利用するための、
* サーバ側 PHP スクリプトのサンプルです.
*
* 本スクリプトを動作させるためには、次の SQL構文で作成された SQLite3 データベース
* (スクリプト中の $db_file)が必要となります.
*
* CREATE TABLE client (id TEXT, key TEXT, flags INTEGER, payload BLOB, md5 TEXT);
* CREATE INDEX index_client ON client (id);
*
* ここで、id は、各センサーデバイス毎に設定する PD Web の ID, key はトークンを
* 作成するための鍵、contens は PD Repeater を介して各センサーデバイスへ送信する
* JSON 文字列(ペイロード)、
* md5 は JSON文字列のMD5ハッシュ値、flags はペイロードの送信状態を示すコードで
* 0:送信済、1:処理中、2:未送信 を意味します.
*
* 以下に初期値の設定例を示します.
*
* INSERT INTO client(id, key, flags, payload, md5)
* VALUES('id00', 'key00', 0, '{"any_key":"any_value"}',
* 'd29e8a13452e5bc5218d9df7e6ea991f');"
* INSERT INTO client(id, key, flags, payload, md5)
* VALUES('id01', 'key10', 0, '{"any_key":"any_value"}',
* 'd29e8a13452e5bc5218d9df7e6ea991f');"
*
* ここで、d29e8a13452e5bc5218d9df7e6ea991 は、'{"any_key":"any_value"}' の MD5値です.
* Linux OS では、次のコマンドで取得することができます.
*
* echo -n '{"any_key":"any_value"}' | md5sum
*
* ペイロードを送信するには SQLの UPDATEを用いて flags を 2:未送信 に変更します.
*
* UPDATE client SET flags = 2 WHERE id = 'id00';
*
* 勿論ペイロードとMD5値を合わせてセットすることも可能です.
*
* UPDATE client
* SET flags = 2,
* payload = '{"any_key":"new_value"}',
* md5 = '94f030ebd7bed4a5ee08fc6fa75ae64e'
* WHERE id = 'id00';
*
* 送信を終えるを PHP スクリプトは flag を 1 に更新し、'reply_to' キーを含む
* 応答ペイロードを待ちます.
*
* 応答ペイロードの例
*
* {"reply_to":"94f030ebd7bed4a5ee08fc6fa75ae64e","result":"done"}
*
* 応答ペイロードは PD Repeater を介してペイロードを受け取る各センサーデバイス
* (のハンドラ)がPD Repeater 介して返すものです.
* PD Agent や PD Handler Modbus は 'reply_to' キーで payload の MD5値をハンドリング
* しますが、独自のハンドラを用いる場合は、独自の応答ペイロードとすることも可能です.
*
* 応答ペイロード受け取ると PHP スクリプトは、データベース上の md5 と reply_to の値を比較し、
* flag を 0 に更新します.
* 受信ペイロードに応答ペイロードが含まれていない場合、PHP スクリプトは flag を 2 に戻し、
* 再送します.
*
*/
$dump_file = '/tmp/dump.txt';
$db_file = '/tmp/pd_web.db';
/* HTTP ヘッダーの確認 */
if (!(isset($_SERVER['HTTP_X_PD_WEB_VERSION']) &&
isset($_SERVER['HTTP_X_PD_WEB_ID']) &&
isset($_SERVER['HTTP_X_PD_WEB_TIME']) &&
isset($_SERVER['HTTP_X_PD_WEB_MD5']) &&
isset($_SERVER['HTTP_X_PD_WEB_SIGNATURE']))) {
/* HTTPヘッダーが不正な場合 Bad Request 400 を返す. */
http_response_code (400);
exit;
}
/* SQLite3 データーベースの読み込み */
/* 本コードはサンプルであるため、SQL インジェクション対策を省略しています.
実運用に利用するためには $_SERVER['HTTP_X_PD_WEB_ID'] のバリデーション
を十分行って下さい. */
$db = new SQLite3($db_file);
$query = sprintf("SELECT key, flags, payload, md5 FROM client WHERE id = '%s';",
$_SERVER['HTTP_X_PD_WEB_ID']);
$results = $db->query($query);
if(! $results) {
/* HTTP_X_PD_WEB_ID が存在しない場合は Unauthorized 401 を返す. */
http_response_code (401);
$db->close();
exit;
}
else {
$row = $results->fetchArray();
$key = $row['key'];
$flags = $row['flags'];
$payload_tx = $row['payload'];
$md5_tx = $row['md5'];
}
/* HTTP リクエストヘッダ内の所定の文字列とデータベース上の key を用い
signature を作成する */
$hash_hmac_data =
$_SERVER['HTTP_X_PD_WEB_VERSION'] .
$_SERVER['HTTP_X_PD_WEB_ID'] .
$_SERVER['HTTP_X_PD_WEB_TIME'] .
$_SERVER['HTTP_X_PD_WEB_MD5'];
$signature = hash_hmac ('sha256', $hash_hmac_data, $key, false);
/* HTTP 応答ヘッダ共通部の設定 */
date_default_timezone_set('Asia/Tokyo');
$tm = localtime();
$timestamp = sprintf("%04d-%02d-%02dT%02d:%02d:%02d.000+09:00",
$tm[5]+1900,$tm[4]+1,$tm[3],$tm[2],$tm[1],$tm[0]);
header('Pd_Web_Version: 1.0');
header('Pd_Web_Id: ' . $_SERVER['HTTP_X_PD_WEB_ID']);
header('Pd_Web_Time: ' . $timestamp);
header('Content-Type: application/json;charset=UTF-8');
/* リクエストヘッダの被署名文字列が VERSION.ID.TIME.MD5 で構成されているのに対し、
応答ヘッダの被署名文字列は、VERSION.ID.TIME.MD5.SIGNATURE
(SIGNATUREは、リクエストヘッダに含まれる文字列) で構成されている点に注意 */
if ($signature != $_SERVER['HTTP_X_PD_WEB_SIGNATURE']) {
/* HTTP_X_PD_WEB_SIGNATURE と signature が一致しない場合は、
401 Unauthorized を返す. */
header('Pd_Web_Md5: ' . md5(''));
$hash_hmac_data = '1.0' . $_SERVER['HTTP_X_PD_WEB_ID'] . $timestamp .
md5('') . $_SERVER['HTTP_X_PD_WEB_SIGNATURE'];
$signature = hash_hmac ('sha256', $hash_hmac_data, $key, false);
header('Pd_Web_Signature: ' . $signature);
http_response_code (401);
$db->close();
exit;
}
$payload = file_get_contents("php://input");
if(md5($payload) != $_SERVER['HTTP_X_PD_WEB_MD5']) {
/* payload の MD5値 と HTTP_X_PD_WEB_MD5 が一致しない場合は、
406 Not Acceptable を返す. */
header('Pd_Web_Md5: ' . md5(''));
$hash_hmac_data = '1.0' . $_SERVER['HTTP_X_PD_WEB_ID'] . $timestamp .
md5('') . $_SERVER['HTTP_X_PD_WEB_SIGNATURE'];
$signature = hash_hmac ('sha256', $hash_hmac_data, $key, false);
header('Pd_Web_Signature: ' . $signature);
http_response_code (406);
$db->close();
exit;
}
/* 受信ペイロードを HTTP リクエストヘッダと共に dump_file に格納する */
$fp = fopen($dump_file, 'a+');
fputs($fp, $_SERVER['HTTP_X_PD_WEB_VERSION']);
fputs($fp, "\n");
fputs($fp, $_SERVER['HTTP_X_PD_WEB_ID']);
fputs($fp, "\n");
fputs($fp, $_SERVER['HTTP_X_PD_WEB_TIME']);
fputs($fp, "\n");
fputs($fp, $_SERVER['HTTP_X_PD_WEB_MD5']);
fputs($fp, "\n");
fputs($fp, $_SERVER['HTTP_X_PD_WEB_SIGNATURE']);
fputs($fp, "\n");
fputs($fp, $_SERVER['CONTENT_LENGTH']);
fputs($fp, "\n");
fputs($fp, $payload);
fputs($fp, "\n");
fputs($fp, "\n");
fclose( $fp );
/* flags が 1 (処理中ペイロードあり) の場合、受信ペイロードから、
'reply_to' キーの値を取得 */
if($flags === 1) {
$json = mb_convert_encoding(
$payload, 'UTF8', 'ASCII,JIS,UTF-8,EUC-JP,SJIS-WIN');
$array = json_decode($json,true);
if ($array !== NULL) {
/* 受信ペイロードは、JSONオブジェクトの配列です. */
$payload_count = count($array);
for($i=0;$i<$payload_count;$i++) {
if (isset($array[$i]['reply_to'])) {
/* 'reply_to' キーの値と md5_tx を比較 */
if($array[$i]['reply_to'] === $md5_tx) {
/* 一致している場合は flags を 0 にする */
$flags = 0;
}
else {
/* 一致していない場合は flags を 2 にする */
$flags = 2;
}
}
break;
}
if($i == $payload_count) {
/* 'reply_to' キーが存在しない場合は flags を 2 にする */
$flags = 2;
}
}
else {
/* JSON文字列で無い場合、flags を 2 にする */
$flags = 2;
}
/* データベースの flags を更新する. */
$query = sprintf("UPDATE client SET flags = %d WHERE id = '%s';" ,
$flags, $_SERVER['HTTP_X_PD_WEB_ID']);
$results = $db->query($query);
}
if($flags == 2) {
/* flags が 2 (送信ペイロードあり) の場合は、payload_tx を送り、200 OK を返す. */
header('Pd_Web_Md5: ' . md5($payload_tx));
$hash_hmac_data = '1.0' . $_SERVER['HTTP_X_PD_WEB_ID'] . $timestamp .
md5($payload_tx) . $_SERVER['HTTP_X_PD_WEB_SIGNATURE'];
$signature = hash_hmac ('sha256', $hash_hmac_data, $key, false);
header('Pd_Web_Signature: ' . $signature);
echo $payload_tx;
http_response_code (200);
/* flags を 1 (処理中ペイロードあり) に変更し、データベースの flags を更新する. */
$flags = 1;
$query = sprintf("UPDATE client SET flags = %d WHERE id = '%s';" ,
$flags, $_SERVER['HTTP_X_PD_WEB_ID']);
$results = $db->query($query);
}
else {
/* 200 OK を返す. */
header('Pd_Web_Md5: ' . md5(''));
$hash_hmac_data = '1.0' . $_SERVER['HTTP_X_PD_WEB_ID'] . $timestamp .
md5('') . $_SERVER['HTTP_X_PD_WEB_SIGNATURE'];
$signature = hash_hmac ('sha256', $hash_hmac_data, $key, false);
header('Pd_Web_Signature: ' . $signature);
http_response_code (200);
}
$db->close();
exit;
?>