19a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey/*
29a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * Copyright (C) 2013 The Android Open Source Project
39a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey *
49a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * Licensed under the Apache License, Version 2.0 (the "License");
59a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * you may not use this file except in compliance with the License.
69a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * You may obtain a copy of the License at
79a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey *
89a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey *      http://www.apache.org/licenses/LICENSE-2.0
99a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey *
109a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * Unless required by applicable law or agreed to in writing, software
119a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * distributed under the License is distributed on an "AS IS" BASIS,
129a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
139a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * See the License for the specific language governing permissions and
149a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey * limitations under the License.
159a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey */
169a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
179a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#define LOG_TAG "NetworkStats"
189a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
199a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <errno.h>
209a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <sys/stat.h>
219a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <sys/types.h>
229a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
239a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <android_runtime/AndroidRuntime.h>
249a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <cutils/logger.h>
259a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <jni.h>
269a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
279a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <ScopedUtfChars.h>
289a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <ScopedLocalRef.h>
299a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <ScopedPrimitiveArray.h>
309a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
319a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <utils/Log.h>
329a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <utils/misc.h>
339a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey#include <utils/Vector.h>
349a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
359a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeynamespace android {
369a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
379a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeystatic jclass gStringClass;
389a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
399a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeystatic struct {
409a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID size;
419a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID iface;
429a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID uid;
439a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID set;
449a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID tag;
459a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID rxBytes;
469a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID rxPackets;
479a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID txBytes;
489a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID txPackets;
499a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jfieldID operations;
509a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey} gNetworkStatsClassInfo;
519a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
529a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeystruct stats_line {
539a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int32_t idx;
549a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    char iface[32];
559a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int32_t uid;
569a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int32_t set;
579a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int32_t tag;
589a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int64_t rxBytes;
599a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int64_t rxPackets;
609a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int64_t txBytes;
619a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int64_t txPackets;
629a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey};
639a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
649a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeystatic int readNetworkStatsDetail(JNIEnv* env, jclass clazz, jobject stats,
659a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        jstring path, jint limitUid) {
669a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedUtfChars path8(env, path);
679a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (path8.c_str() == NULL) {
689a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        return -1;
699a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    }
709a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
719a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    FILE *fp = fopen(path8.c_str(), "r");
729a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (fp == NULL) {
739a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        return -1;
749a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    }
759a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
769a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    Vector<stats_line> lines;
779a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
789a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int lastIdx = 1;
799a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    char buffer[384];
809a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    while (fgets(buffer, sizeof(buffer), fp) != NULL) {
819a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        stats_line s;
829a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        int64_t rawTag;
839a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        if (sscanf(buffer, "%d %31s 0x%llx %u %u %llu %llu %llu %llu", &s.idx,
849a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey                &s.iface, &rawTag, &s.uid, &s.set, &s.rxBytes, &s.rxPackets,
859a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey                &s.txBytes, &s.txPackets) == 9) {
869a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            if (s.idx != lastIdx + 1) {
879a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey                ALOGE("inconsistent idx=%d after lastIdx=%d", s.idx, lastIdx);
889a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey                return -1;
899a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            }
909a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            lastIdx = s.idx;
919a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
929a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            s.tag = rawTag >> 32;
939a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            lines.push_back(s);
949a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        }
959a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    }
969a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
979a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (fclose(fp) != 0) {
989a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        return -1;
999a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    }
1009a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1019a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int size = lines.size();
1029a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1039a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLocalRef<jobjectArray> iface(env, env->NewObjectArray(size, gStringClass, NULL));
1049a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (iface.get() == NULL) return -1;
1059a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedIntArrayRW uid(env, env->NewIntArray(size));
1069a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (uid.get() == NULL) return -1;
1079a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedIntArrayRW set(env, env->NewIntArray(size));
1089a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (set.get() == NULL) return -1;
1099a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedIntArrayRW tag(env, env->NewIntArray(size));
1109a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (tag.get() == NULL) return -1;
1119a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLongArrayRW rxBytes(env, env->NewLongArray(size));
1129a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (rxBytes.get() == NULL) return -1;
1139a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLongArrayRW rxPackets(env, env->NewLongArray(size));
1149a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (rxPackets.get() == NULL) return -1;
1159a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLongArrayRW txBytes(env, env->NewLongArray(size));
1169a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (txBytes.get() == NULL) return -1;
1179a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLongArrayRW txPackets(env, env->NewLongArray(size));
1189a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (txPackets.get() == NULL) return -1;
1199a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLongArrayRW operations(env, env->NewLongArray(size));
1209a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (operations.get() == NULL) return -1;
1219a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1229a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    for (int i = 0; i < size; i++) {
1239a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        ScopedLocalRef<jstring> ifaceString(env, env->NewStringUTF(lines[i].iface));
1249a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        env->SetObjectArrayElement(iface.get(), i, ifaceString.get());
1259a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1269a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        uid[i] = lines[i].uid;
1279a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        set[i] = lines[i].set;
1289a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        tag[i] = lines[i].tag;
1299a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        rxBytes[i] = lines[i].rxBytes;
1309a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        rxPackets[i] = lines[i].rxPackets;
1319a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        txBytes[i] = lines[i].txBytes;
1329a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        txPackets[i] = lines[i].txPackets;
1339a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    }
1349a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1359a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetIntField(stats, gNetworkStatsClassInfo.size, size);
1369a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.iface, iface.get());
1379a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.uid, uid.getJavaArray());
1389a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.set, set.getJavaArray());
1399a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.tag, tag.getJavaArray());
1409a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.rxBytes, rxBytes.getJavaArray());
1419a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.rxPackets, rxPackets.getJavaArray());
1429a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.txBytes, txBytes.getJavaArray());
1439a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.txPackets, txPackets.getJavaArray());
1449a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    env->SetObjectField(stats, gNetworkStatsClassInfo.operations, operations.getJavaArray());
1459a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1469a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    return 0;
1479a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey}
1489a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1499a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeystatic jclass findClass(JNIEnv* env, const char* name) {
1509a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    ScopedLocalRef<jclass> localClass(env, env->FindClass(name));
1519a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jclass result = reinterpret_cast<jclass>(env->NewGlobalRef(localClass.get()));
1529a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    if (result == NULL) {
1539a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        ALOGE("failed to find class '%s'", name);
1549a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        abort();
1559a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    }
1569a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    return result;
1579a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey}
1589a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1599a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeystatic JNINativeMethod gMethods[] = {
1609a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey        { "nativeReadNetworkStatsDetail",
1619a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey                "(Landroid/net/NetworkStats;Ljava/lang/String;I)I",
1629a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey                (void*) readNetworkStatsDetail }
1639a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey};
1649a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1659a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkeyint register_com_android_internal_net_NetworkStatsFactory(JNIEnv* env) {
1669a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    int err = AndroidRuntime::registerNativeMethods(env,
1679a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            "com/android/internal/net/NetworkStatsFactory", gMethods,
1689a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey            NELEM(gMethods));
1699a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1709a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gStringClass = findClass(env, "java/lang/String");
1719a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1729a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    jclass clazz = env->FindClass("android/net/NetworkStats");
1739a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.size = env->GetFieldID(clazz, "size", "I");
1749a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.iface = env->GetFieldID(clazz, "iface", "[Ljava/lang/String;");
1759a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.uid = env->GetFieldID(clazz, "uid", "[I");
1769a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.set = env->GetFieldID(clazz, "set", "[I");
1779a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.tag = env->GetFieldID(clazz, "tag", "[I");
1789a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.rxBytes = env->GetFieldID(clazz, "rxBytes", "[J");
1799a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.rxPackets = env->GetFieldID(clazz, "rxPackets", "[J");
1809a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.txBytes = env->GetFieldID(clazz, "txBytes", "[J");
1819a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.txPackets = env->GetFieldID(clazz, "txPackets", "[J");
1829a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    gNetworkStatsClassInfo.operations = env->GetFieldID(clazz, "operations", "[J");
1839a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1849a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey    return err;
1859a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey}
1869a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey
1879a2c2a6da90abbcc9a064c20e93ed885651f4ae1Jeff Sharkey}
188